added support for optional Model and Record event hook tags
This commit is contained in:
		
							parent
							
								
									32af49dbec
								
							
						
					
					
						commit
						b8d7609e9e
					
				|  | @ -56,6 +56,8 @@ | ||||||
|   store.GetAll() map[string]T |   store.GetAll() map[string]T | ||||||
|   ``` |   ``` | ||||||
| 
 | 
 | ||||||
|  | - Added tagged/proxy hook for all Record and Model events (@todo document). | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ## v0.11.4 | ## v0.11.4 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -39,11 +39,10 @@ func (api *adminApi) authResponse(c echo.Context, admin *models.Admin) error { | ||||||
| 		return NewBadRequestError("Failed to create auth token.", tokenErr) | 		return NewBadRequestError("Failed to create auth token.", tokenErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminAuthEvent{ | 	event := new(core.AdminAuthEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admin:       admin, | 	event.Admin = admin | ||||||
| 		Token:       token, | 	event.Token = token | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnAdminAuthRequest().Trigger(event, func(e *core.AdminAuthEvent) error { | 	return api.app.OnAdminAuthRequest().Trigger(event, func(e *core.AdminAuthEvent) error { | ||||||
| 		return e.HttpContext.JSON(200, map[string]any{ | 		return e.HttpContext.JSON(200, map[string]any{ | ||||||
|  | @ -59,10 +58,9 @@ func (api *adminApi) authRefresh(c echo.Context) error { | ||||||
| 		return NewNotFoundError("Missing auth admin context.", nil) | 		return NewNotFoundError("Missing auth admin context.", nil) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminAuthRefreshEvent{ | 	event := new(core.AdminAuthRefreshEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admin:       admin, | 	event.Admin = admin | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	handlerErr := api.app.OnAdminBeforeAuthRefreshRequest().Trigger(event, func(e *core.AdminAuthRefreshEvent) error { | 	handlerErr := api.app.OnAdminBeforeAuthRefreshRequest().Trigger(event, func(e *core.AdminAuthRefreshEvent) error { | ||||||
| 		return api.authResponse(e.HttpContext, e.Admin) | 		return api.authResponse(e.HttpContext, e.Admin) | ||||||
|  | @ -83,11 +81,10 @@ func (api *adminApi) authWithPassword(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", err) | 		return NewBadRequestError("An error occurred while loading the submitted data.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminAuthWithPasswordEvent{ | 	event := new(core.AdminAuthWithPasswordEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Password:    form.Password, | 	event.Password = form.Password | ||||||
| 		Identity:    form.Identity, | 	event.Identity = form.Identity | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | ||||||
| 		return func(admin *models.Admin) error { | 		return func(admin *models.Admin) error { | ||||||
|  | @ -122,9 +119,8 @@ func (api *adminApi) requestPasswordReset(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while validating the form.", err) | 		return NewBadRequestError("An error occurred while validating the form.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminRequestPasswordResetEvent{ | 	event := new(core.AdminRequestPasswordResetEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | ||||||
| 		return func(Admin *models.Admin) error { | 		return func(Admin *models.Admin) error { | ||||||
|  | @ -165,9 +161,8 @@ func (api *adminApi) confirmPasswordReset(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminConfirmPasswordResetEvent{ | 	event := new(core.AdminConfirmPasswordResetEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | ||||||
| 		return func(admin *models.Admin) error { | 		return func(admin *models.Admin) error { | ||||||
|  | @ -207,11 +202,10 @@ func (api *adminApi) list(c echo.Context) error { | ||||||
| 		return NewBadRequestError("", err) | 		return NewBadRequestError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminsListEvent{ | 	event := new(core.AdminsListEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admins:      admins, | 	event.Admins = admins | ||||||
| 		Result:      result, | 	event.Result = result | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnAdminsListRequest().Trigger(event, func(e *core.AdminsListEvent) error { | 	return api.app.OnAdminsListRequest().Trigger(event, func(e *core.AdminsListEvent) error { | ||||||
| 		return e.HttpContext.JSON(http.StatusOK, e.Result) | 		return e.HttpContext.JSON(http.StatusOK, e.Result) | ||||||
|  | @ -229,10 +223,9 @@ func (api *adminApi) view(c echo.Context) error { | ||||||
| 		return NewNotFoundError("", err) | 		return NewNotFoundError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminViewEvent{ | 	event := new(core.AdminViewEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admin:       admin, | 	event.Admin = admin | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnAdminViewRequest().Trigger(event, func(e *core.AdminViewEvent) error { | 	return api.app.OnAdminViewRequest().Trigger(event, func(e *core.AdminViewEvent) error { | ||||||
| 		return e.HttpContext.JSON(http.StatusOK, e.Admin) | 		return e.HttpContext.JSON(http.StatusOK, e.Admin) | ||||||
|  | @ -249,10 +242,9 @@ func (api *adminApi) create(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminCreateEvent{ | 	event := new(core.AdminCreateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admin:       admin, | 	event.Admin = admin | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// create the admin
 | 	// create the admin
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | ||||||
|  | @ -296,10 +288,9 @@ func (api *adminApi) update(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminUpdateEvent{ | 	event := new(core.AdminUpdateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admin:       admin, | 	event.Admin = admin | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// update the admin
 | 	// update the admin
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { | ||||||
|  | @ -336,10 +327,9 @@ func (api *adminApi) delete(c echo.Context) error { | ||||||
| 		return NewNotFoundError("", err) | 		return NewNotFoundError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.AdminDeleteEvent{ | 	event := new(core.AdminDeleteEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Admin:       admin, | 	event.Admin = admin | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	handlerErr := api.app.OnAdminBeforeDeleteRequest().Trigger(event, func(e *core.AdminDeleteEvent) error { | 	handlerErr := api.app.OnAdminBeforeDeleteRequest().Trigger(event, func(e *core.AdminDeleteEvent) error { | ||||||
| 		if err := api.app.Dao().DeleteAdmin(e.Admin); err != nil { | 		if err := api.app.Dao().DeleteAdmin(e.Admin); err != nil { | ||||||
|  |  | ||||||
|  | @ -71,10 +71,9 @@ func InitApi(app core.App) (*echo.Echo, error) { | ||||||
| 			apiErr = NewBadRequestError("", err) | 			apiErr = NewBadRequestError("", err) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		event := &core.ApiErrorEvent{ | 		event := new(core.ApiErrorEvent) | ||||||
| 			HttpContext: c, | 		event.HttpContext = c | ||||||
| 			Error:       apiErr, | 		event.Error = apiErr | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		// send error response
 | 		// send error response
 | ||||||
| 		hookErr := app.OnBeforeApiError().Trigger(event, func(e *core.ApiErrorEvent) error { | 		hookErr := app.OnBeforeApiError().Trigger(event, func(e *core.ApiErrorEvent) error { | ||||||
|  |  | ||||||
|  | @ -43,11 +43,10 @@ func (api *collectionApi) list(c echo.Context) error { | ||||||
| 		return NewBadRequestError("", err) | 		return NewBadRequestError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.CollectionsListEvent{ | 	event := new(core.CollectionsListEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collections: collections, | 	event.Collections = collections | ||||||
| 		Result:      result, | 	event.Result = result | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnCollectionsListRequest().Trigger(event, func(e *core.CollectionsListEvent) error { | 	return api.app.OnCollectionsListRequest().Trigger(event, func(e *core.CollectionsListEvent) error { | ||||||
| 		return e.HttpContext.JSON(http.StatusOK, e.Result) | 		return e.HttpContext.JSON(http.StatusOK, e.Result) | ||||||
|  | @ -60,10 +59,9 @@ func (api *collectionApi) view(c echo.Context) error { | ||||||
| 		return NewNotFoundError("", err) | 		return NewNotFoundError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.CollectionViewEvent{ | 	event := new(core.CollectionViewEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collection:  collection, | 	event.Collection = collection | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnCollectionViewRequest().Trigger(event, func(e *core.CollectionViewEvent) error { | 	return api.app.OnCollectionViewRequest().Trigger(event, func(e *core.CollectionViewEvent) error { | ||||||
| 		return e.HttpContext.JSON(http.StatusOK, e.Collection) | 		return e.HttpContext.JSON(http.StatusOK, e.Collection) | ||||||
|  | @ -80,10 +78,9 @@ func (api *collectionApi) create(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.CollectionCreateEvent{ | 	event := new(core.CollectionCreateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collection:  collection, | 	event.Collection = collection | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// create the collection
 | 	// create the collection
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] { | ||||||
|  | @ -122,10 +119,9 @@ func (api *collectionApi) update(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.CollectionUpdateEvent{ | 	event := new(core.CollectionUpdateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collection:  collection, | 	event.Collection = collection | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// update the collection
 | 	// update the collection
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Collection]) forms.InterceptorNextFunc[*models.Collection] { | ||||||
|  | @ -157,10 +153,9 @@ func (api *collectionApi) delete(c echo.Context) error { | ||||||
| 		return NewNotFoundError("", err) | 		return NewNotFoundError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.CollectionDeleteEvent{ | 	event := new(core.CollectionDeleteEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collection:  collection, | 	event.Collection = collection | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	handlerErr := api.app.OnCollectionBeforeDeleteRequest().Trigger(event, func(e *core.CollectionDeleteEvent) error { | 	handlerErr := api.app.OnCollectionBeforeDeleteRequest().Trigger(event, func(e *core.CollectionDeleteEvent) error { | ||||||
| 		if err := api.app.Dao().DeleteCollection(e.Collection); err != nil { | 		if err := api.app.Dao().DeleteCollection(e.Collection); err != nil { | ||||||
|  | @ -187,10 +182,9 @@ func (api *collectionApi) bulkImport(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.CollectionsImportEvent{ | 	event := new(core.CollectionsImportEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collections: form.Collections, | 	event.Collections = form.Collections | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// import collections
 | 	// import collections
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[[]*models.Collection]) forms.InterceptorNextFunc[[]*models.Collection] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[[]*models.Collection]) forms.InterceptorNextFunc[[]*models.Collection] { | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								apis/file.go
								
								
								
								
							
							
						
						
									
										15
									
								
								apis/file.go
								
								
								
								
							|  | @ -84,14 +84,13 @@ func (api *fileApi) download(c echo.Context) error { | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.FileDownloadEvent{ | 	event := new(core.FileDownloadEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = collection | ||||||
| 		Collection:  collection, | 	event.Record = record | ||||||
| 		FileField:   fileField, | 	event.FileField = fileField | ||||||
| 		ServedPath:  servedPath, | 	event.ServedPath = servedPath | ||||||
| 		ServedName:  servedName, | 	event.ServedName = servedName | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnFileDownloadRequest().Trigger(event, func(e *core.FileDownloadEvent) error { | 	return api.app.OnFileDownloadRequest().Trigger(event, func(e *core.FileDownloadEvent) error { | ||||||
| 		res := e.HttpContext.Response() | 		res := e.HttpContext.Response() | ||||||
|  |  | ||||||
|  | @ -55,10 +55,10 @@ func (api *recordAuthApi) authRefresh(c echo.Context) error { | ||||||
| 		return NewNotFoundError("Missing auth record context.", nil) | 		return NewNotFoundError("Missing auth record context.", nil) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordAuthRefreshEvent{ | 	event := new(core.RecordAuthRefreshEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = record.Collection() | ||||||
| 	} | 	event.Record = record | ||||||
| 
 | 
 | ||||||
| 	handlerErr := api.app.OnRecordBeforeAuthRefreshRequest().Trigger(event, func(e *core.RecordAuthRefreshEvent) error { | 	handlerErr := api.app.OnRecordBeforeAuthRefreshRequest().Trigger(event, func(e *core.RecordAuthRefreshEvent) error { | ||||||
| 		return RecordAuthResponse(api.app, e.HttpContext, e.Record, nil) | 		return RecordAuthResponse(api.app, e.HttpContext, e.Record, nil) | ||||||
|  | @ -204,9 +204,9 @@ func (api *recordAuthApi) authWithOAuth2(c echo.Context) error { | ||||||
| 		}) | 		}) | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordAuthWithOAuth2Event{ | 	event := new(core.RecordAuthWithOAuth2Event) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} | 	event.Collection = collection | ||||||
| 
 | 
 | ||||||
| 	_, _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*forms.RecordOAuth2LoginData]) forms.InterceptorNextFunc[*forms.RecordOAuth2LoginData] { | 	_, _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*forms.RecordOAuth2LoginData]) forms.InterceptorNextFunc[*forms.RecordOAuth2LoginData] { | ||||||
| 		return func(data *forms.RecordOAuth2LoginData) error { | 		return func(data *forms.RecordOAuth2LoginData) error { | ||||||
|  | @ -249,11 +249,11 @@ func (api *recordAuthApi) authWithPassword(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordAuthWithPasswordEvent{ | 	event := new(core.RecordAuthWithPasswordEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Password:    form.Password, | 	event.Collection = collection | ||||||
| 		Identity:    form.Identity, | 	event.Password = form.Password | ||||||
| 	} | 	event.Identity = form.Identity | ||||||
| 
 | 
 | ||||||
| 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -298,9 +298,9 @@ func (api *recordAuthApi) requestPasswordReset(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while validating the form.", err) | 		return NewBadRequestError("An error occurred while validating the form.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordRequestPasswordResetEvent{ | 	event := new(core.RecordRequestPasswordResetEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} | 	event.Collection = collection | ||||||
| 
 | 
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -346,9 +346,9 @@ func (api *recordAuthApi) confirmPasswordReset(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordConfirmPasswordResetEvent{ | 	event := new(core.RecordConfirmPasswordResetEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} | 	event.Collection = collection | ||||||
| 
 | 
 | ||||||
| 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -388,9 +388,9 @@ func (api *recordAuthApi) requestVerification(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while validating the form.", err) | 		return NewBadRequestError("An error occurred while validating the form.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordRequestVerificationEvent{ | 	event := new(core.RecordRequestVerificationEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} | 	event.Collection = collection | ||||||
| 
 | 
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -436,9 +436,9 @@ func (api *recordAuthApi) confirmVerification(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordConfirmVerificationEvent{ | 	event := new(core.RecordConfirmVerificationEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} | 	event.Collection = collection | ||||||
| 
 | 
 | ||||||
| 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -464,6 +464,11 @@ func (api *recordAuthApi) confirmVerification(c echo.Context) error { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (api *recordAuthApi) requestEmailChange(c echo.Context) error { | func (api *recordAuthApi) requestEmailChange(c echo.Context) error { | ||||||
|  | 	collection, _ := c.Get(ContextCollectionKey).(*models.Collection) | ||||||
|  | 	if collection == nil { | ||||||
|  | 		return NewNotFoundError("Missing collection context.", nil) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	record, _ := c.Get(ContextAuthRecordKey).(*models.Record) | 	record, _ := c.Get(ContextAuthRecordKey).(*models.Record) | ||||||
| 	if record == nil { | 	if record == nil { | ||||||
| 		return NewUnauthorizedError("The request requires valid auth record.", nil) | 		return NewUnauthorizedError("The request requires valid auth record.", nil) | ||||||
|  | @ -474,10 +479,10 @@ func (api *recordAuthApi) requestEmailChange(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", err) | 		return NewBadRequestError("An error occurred while loading the submitted data.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordRequestEmailChangeEvent{ | 	event := new(core.RecordRequestEmailChangeEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = collection | ||||||
| 	} | 	event.Record = record | ||||||
| 
 | 
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -509,9 +514,9 @@ func (api *recordAuthApi) confirmEmailChange(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | 		return NewBadRequestError("An error occurred while loading the submitted data.", readErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordConfirmEmailChangeEvent{ | 	event := new(core.RecordConfirmEmailChangeEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 	} | 	event.Collection = collection | ||||||
| 
 | 
 | ||||||
| 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	_, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
| 		return func(record *models.Record) error { | 		return func(record *models.Record) error { | ||||||
|  | @ -557,11 +562,11 @@ func (api *recordAuthApi) listExternalAuths(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to fetch the external auths for the specified auth record.", err) | 		return NewBadRequestError("Failed to fetch the external auths for the specified auth record.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordListExternalAuthsEvent{ | 	event := new(core.RecordListExternalAuthsEvent) | ||||||
| 		HttpContext:   c, | 	event.HttpContext = c | ||||||
| 		Record:        record, | 	event.Collection = collection | ||||||
| 		ExternalAuths: externalAuths, | 	event.Record = record | ||||||
| 	} | 	event.ExternalAuths = externalAuths | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnRecordListExternalAuthsRequest().Trigger(event, func(e *core.RecordListExternalAuthsEvent) error { | 	return api.app.OnRecordListExternalAuthsRequest().Trigger(event, func(e *core.RecordListExternalAuthsEvent) error { | ||||||
| 		return e.HttpContext.JSON(http.StatusOK, e.ExternalAuths) | 		return e.HttpContext.JSON(http.StatusOK, e.ExternalAuths) | ||||||
|  | @ -590,11 +595,11 @@ func (api *recordAuthApi) unlinkExternalAuth(c echo.Context) error { | ||||||
| 		return NewNotFoundError("Missing external auth provider relation.", err) | 		return NewNotFoundError("Missing external auth provider relation.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordUnlinkExternalAuthEvent{ | 	event := new(core.RecordUnlinkExternalAuthEvent) | ||||||
| 		HttpContext:  c, | 	event.HttpContext = c | ||||||
| 		Record:       record, | 	event.Collection = collection | ||||||
| 		ExternalAuth: externalAuth, | 	event.Record = record | ||||||
| 	} | 	event.ExternalAuth = externalAuth | ||||||
| 
 | 
 | ||||||
| 	handlerErr := api.app.OnRecordBeforeUnlinkExternalAuthRequest().Trigger(event, func(e *core.RecordUnlinkExternalAuthEvent) error { | 	handlerErr := api.app.OnRecordBeforeUnlinkExternalAuthRequest().Trigger(event, func(e *core.RecordUnlinkExternalAuthEvent) error { | ||||||
| 		if err := api.app.Dao().DeleteExternalAuth(externalAuth); err != nil { | 		if err := api.app.Dao().DeleteExternalAuth(externalAuth); err != nil { | ||||||
|  |  | ||||||
|  | @ -83,12 +83,11 @@ func (api *recordApi) list(c echo.Context) error { | ||||||
| 
 | 
 | ||||||
| 	result.Items = records | 	result.Items = records | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordsListEvent{ | 	event := new(core.RecordsListEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Collection:  collection, | 	event.Collection = collection | ||||||
| 		Records:     records, | 	event.Records = records | ||||||
| 		Result:      result, | 	event.Result = result | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnRecordsListRequest().Trigger(event, func(e *core.RecordsListEvent) error { | 	return api.app.OnRecordsListRequest().Trigger(event, func(e *core.RecordsListEvent) error { | ||||||
| 		if err := EnrichRecords(e.HttpContext, api.app.Dao(), e.Records); err != nil && api.app.IsDebug() { | 		if err := EnrichRecords(e.HttpContext, api.app.Dao(), e.Records); err != nil && api.app.IsDebug() { | ||||||
|  | @ -135,10 +134,10 @@ func (api *recordApi) view(c echo.Context) error { | ||||||
| 		return NewNotFoundError("", fetchErr) | 		return NewNotFoundError("", fetchErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordViewEvent{ | 	event := new(core.RecordViewEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = collection | ||||||
| 	} | 	event.Record = record | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnRecordViewRequest().Trigger(event, func(e *core.RecordViewEvent) error { | 	return api.app.OnRecordViewRequest().Trigger(event, func(e *core.RecordViewEvent) error { | ||||||
| 		if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil && api.app.IsDebug() { | 		if err := EnrichRecord(e.HttpContext, api.app.Dao(), e.Record); err != nil && api.app.IsDebug() { | ||||||
|  | @ -218,10 +217,10 @@ func (api *recordApi) create(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordCreateEvent{ | 	event := new(core.RecordCreateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = collection | ||||||
| 	} | 	event.Record = record | ||||||
| 
 | 
 | ||||||
| 	// create the record
 | 	// create the record
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
|  | @ -306,10 +305,10 @@ func (api *recordApi) update(c echo.Context) error { | ||||||
| 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | 		return NewBadRequestError("Failed to load the submitted data due to invalid formatting.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordUpdateEvent{ | 	event := new(core.RecordUpdateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = collection | ||||||
| 	} | 	event.Record = record | ||||||
| 
 | 
 | ||||||
| 	// update the record
 | 	// update the record
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { | ||||||
|  | @ -375,10 +374,10 @@ func (api *recordApi) delete(c echo.Context) error { | ||||||
| 		return NewNotFoundError("", fetchErr) | 		return NewNotFoundError("", fetchErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordDeleteEvent{ | 	event := new(core.RecordDeleteEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      record, | 	event.Collection = collection | ||||||
| 	} | 	event.Record = record | ||||||
| 
 | 
 | ||||||
| 	handlerErr := api.app.OnRecordBeforeDeleteRequest().Trigger(event, func(e *core.RecordDeleteEvent) error { | 	handlerErr := api.app.OnRecordBeforeDeleteRequest().Trigger(event, func(e *core.RecordDeleteEvent) error { | ||||||
| 		// delete the record
 | 		// delete the record
 | ||||||
|  |  | ||||||
|  | @ -51,12 +51,12 @@ func RecordAuthResponse(app core.App, c echo.Context, authRecord *models.Record, | ||||||
| 		return NewBadRequestError("Failed to create auth token.", tokenErr) | 		return NewBadRequestError("Failed to create auth token.", tokenErr) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.RecordAuthEvent{ | 	event := new(core.RecordAuthEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		Record:      authRecord, | 	event.Collection = authRecord.Collection() | ||||||
| 		Token:       token, | 	event.Record = authRecord | ||||||
| 		Meta:        meta, | 	event.Token = token | ||||||
| 	} | 	event.Meta = meta | ||||||
| 
 | 
 | ||||||
| 	return app.OnRecordAuthRequest().Trigger(event, func(e *core.RecordAuthEvent) error { | 	return app.OnRecordAuthRequest().Trigger(event, func(e *core.RecordAuthEvent) error { | ||||||
| 		// allow always returning the email address of the authenticated account
 | 		// allow always returning the email address of the authenticated account
 | ||||||
|  | @ -93,17 +93,17 @@ func RecordAuthResponse(app core.App, c echo.Context, authRecord *models.Record, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // EnrichRecord parses the request context and enrich the provided record:
 | // EnrichRecord parses the request context and enrich the provided record:
 | ||||||
| // - expands relations (if defaultExpands and/or ?expand query param is set)
 | //   - expands relations (if defaultExpands and/or ?expand query param is set)
 | ||||||
| // - ensures that the emails of the auth record and its expanded auth relations
 | //   - ensures that the emails of the auth record and its expanded auth relations
 | ||||||
| //   are visibe only for the current logged admin, record owner or record with manage access
 | //     are visibe only for the current logged admin, record owner or record with manage access
 | ||||||
| func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultExpands ...string) error { | func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultExpands ...string) error { | ||||||
| 	return EnrichRecords(c, dao, []*models.Record{record}, defaultExpands...) | 	return EnrichRecords(c, dao, []*models.Record{record}, defaultExpands...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // EnrichRecords parses the request context and enriches the provided records:
 | // EnrichRecords parses the request context and enriches the provided records:
 | ||||||
| // - expands relations (if defaultExpands and/or ?expand query param is set)
 | //   - expands relations (if defaultExpands and/or ?expand query param is set)
 | ||||||
| // - ensures that the emails of the auth records and their expanded auth relations
 | //   - ensures that the emails of the auth records and their expanded auth relations
 | ||||||
| //   are visibe only for the current logged admin, record owner or record with manage access
 | //     are visibe only for the current logged admin, record owner or record with manage access
 | ||||||
| func EnrichRecords(c echo.Context, dao *daos.Dao, records []*models.Record, defaultExpands ...string) error { | func EnrichRecords(c echo.Context, dao *daos.Dao, records []*models.Record, defaultExpands ...string) error { | ||||||
| 	requestData := RequestData(c) | 	requestData := RequestData(c) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -34,10 +34,9 @@ func (api *settingsApi) list(c echo.Context) error { | ||||||
| 		return NewBadRequestError("", err) | 		return NewBadRequestError("", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.SettingsListEvent{ | 	event := new(core.SettingsListEvent) | ||||||
| 		HttpContext:      c, | 	event.HttpContext = c | ||||||
| 		RedactedSettings: settings, | 	event.RedactedSettings = settings | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	return api.app.OnSettingsListRequest().Trigger(event, func(e *core.SettingsListEvent) error { | 	return api.app.OnSettingsListRequest().Trigger(event, func(e *core.SettingsListEvent) error { | ||||||
| 		return e.HttpContext.JSON(http.StatusOK, e.RedactedSettings) | 		return e.HttpContext.JSON(http.StatusOK, e.RedactedSettings) | ||||||
|  | @ -52,10 +51,9 @@ func (api *settingsApi) set(c echo.Context) error { | ||||||
| 		return NewBadRequestError("An error occurred while loading the submitted data.", err) | 		return NewBadRequestError("An error occurred while loading the submitted data.", err) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.SettingsUpdateEvent{ | 	event := new(core.SettingsUpdateEvent) | ||||||
| 		HttpContext: c, | 	event.HttpContext = c | ||||||
| 		OldSettings: api.app.Settings(), | 	event.OldSettings = api.app.Settings() | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	// update the settings
 | 	// update the settings
 | ||||||
| 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*settings.Settings]) forms.InterceptorNextFunc[*settings.Settings] { | 	submitErr := form.Submit(func(next forms.InterceptorNextFunc[*settings.Settings]) forms.InterceptorNextFunc[*settings.Settings] { | ||||||
|  |  | ||||||
							
								
								
									
										218
									
								
								core/app.go
								
								
								
								
							
							
						
						
									
										218
									
								
								core/app.go
								
								
								
								
							|  | @ -124,27 +124,51 @@ type App interface { | ||||||
| 
 | 
 | ||||||
| 	// OnModelBeforeCreate hook is triggered before inserting a new
 | 	// OnModelBeforeCreate hook is triggered before inserting a new
 | ||||||
| 	// entry in the DB, allowing you to modify or validate the stored data.
 | 	// entry in the DB, allowing you to modify or validate the stored data.
 | ||||||
| 	OnModelBeforeCreate() *hook.Hook[*ModelEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags"
 | ||||||
|  | 	// (table names and/or the Collection id for Record models)
 | ||||||
|  | 	// to filter any the newly attached event data handler.
 | ||||||
|  | 	OnModelBeforeCreate(tags ...string) *hook.TaggedHook[*ModelEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnModelAfterCreate hook is triggered after successfully
 | 	// OnModelAfterCreate hook is triggered after successfully
 | ||||||
| 	// inserting a new entry in the DB.
 | 	// inserting a new entry in the DB.
 | ||||||
| 	OnModelAfterCreate() *hook.Hook[*ModelEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags"
 | ||||||
|  | 	// (table names and/or the Collection id for Record models)
 | ||||||
|  | 	// to filter any the newly attached event data handler.
 | ||||||
|  | 	OnModelAfterCreate(tags ...string) *hook.TaggedHook[*ModelEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnModelBeforeUpdate hook is triggered before updating existing
 | 	// OnModelBeforeUpdate hook is triggered before updating existing
 | ||||||
| 	// entry in the DB, allowing you to modify or validate the stored data.
 | 	// entry in the DB, allowing you to modify or validate the stored data.
 | ||||||
| 	OnModelBeforeUpdate() *hook.Hook[*ModelEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags"
 | ||||||
|  | 	// (table names and/or the Collection id for Record models)
 | ||||||
|  | 	// to filter any the newly attached event data handler.
 | ||||||
|  | 	OnModelBeforeUpdate(tags ...string) *hook.TaggedHook[*ModelEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnModelAfterUpdate hook is triggered after successfully updating
 | 	// OnModelAfterUpdate hook is triggered after successfully updating
 | ||||||
| 	// existing entry in the DB.
 | 	// existing entry in the DB.
 | ||||||
| 	OnModelAfterUpdate() *hook.Hook[*ModelEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags"
 | ||||||
|  | 	// (table names and/or the Collection id for Record models)
 | ||||||
|  | 	// to filter any the newly attached event data handler.
 | ||||||
|  | 	OnModelAfterUpdate(tags ...string) *hook.TaggedHook[*ModelEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnModelBeforeDelete hook is triggered before deleting an
 | 	// OnModelBeforeDelete hook is triggered before deleting an
 | ||||||
| 	// existing entry from the DB.
 | 	// existing entry from the DB.
 | ||||||
| 	OnModelBeforeDelete() *hook.Hook[*ModelEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags"
 | ||||||
|  | 	// (table names and/or the Collection id for Record models)
 | ||||||
|  | 	// to filter any the newly attached event data handler.
 | ||||||
|  | 	OnModelBeforeDelete(tags ...string) *hook.TaggedHook[*ModelEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnModelAfterDelete is triggered after successfully deleting an
 | 	// OnModelAfterDelete is triggered after successfully deleting an
 | ||||||
| 	// existing entry from the DB.
 | 	// existing entry from the DB.
 | ||||||
| 	OnModelAfterDelete() *hook.Hook[*ModelEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags"
 | ||||||
|  | 	// (table names and/or the Collection id for Record models)
 | ||||||
|  | 	// to filter any the newly attached event data handler.
 | ||||||
|  | 	OnModelAfterDelete(tags ...string) *hook.TaggedHook[*ModelEvent] | ||||||
| 
 | 
 | ||||||
| 	// ---------------------------------------------------------------
 | 	// ---------------------------------------------------------------
 | ||||||
| 	// Mailer event hooks
 | 	// Mailer event hooks
 | ||||||
|  | @ -166,33 +190,51 @@ type App interface { | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to send your own custom email template if
 | 	// Could be used to send your own custom email template if
 | ||||||
| 	// [hook.StopPropagation] is returned in one of its listeners.
 | 	// [hook.StopPropagation] is returned in one of its listeners.
 | ||||||
| 	OnMailerBeforeRecordResetPasswordSend() *hook.Hook[*MailerRecordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnMailerBeforeRecordResetPasswordSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnMailerAfterRecordResetPasswordSend hook is triggered after
 | 	// OnMailerAfterRecordResetPasswordSend hook is triggered after
 | ||||||
| 	// an auth record password reset email was successfully sent.
 | 	// an auth record password reset email was successfully sent.
 | ||||||
| 	OnMailerAfterRecordResetPasswordSend() *hook.Hook[*MailerRecordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnMailerAfterRecordResetPasswordSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnMailerBeforeRecordVerificationSend hook is triggered right before
 | 	// OnMailerBeforeRecordVerificationSend hook is triggered right before
 | ||||||
| 	// sending a verification email to an auth record.
 | 	// sending a verification email to an auth record.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to send your own custom email template if
 | 	// Could be used to send your own custom email template if
 | ||||||
| 	// [hook.StopPropagation] is returned in one of its listeners.
 | 	// [hook.StopPropagation] is returned in one of its listeners.
 | ||||||
| 	OnMailerBeforeRecordVerificationSend() *hook.Hook[*MailerRecordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnMailerBeforeRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnMailerAfterRecordVerificationSend hook is triggered after a
 | 	// OnMailerAfterRecordVerificationSend hook is triggered after a
 | ||||||
| 	// verification email was successfully sent to an auth record.
 | 	// verification email was successfully sent to an auth record.
 | ||||||
| 	OnMailerAfterRecordVerificationSend() *hook.Hook[*MailerRecordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnMailerAfterRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnMailerBeforeRecordChangeEmailSend hook is triggered right before
 | 	// OnMailerBeforeRecordChangeEmailSend hook is triggered right before
 | ||||||
| 	// sending a confirmation new address email to an auth record.
 | 	// sending a confirmation new address email to an auth record.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to send your own custom email template if
 | 	// Could be used to send your own custom email template if
 | ||||||
| 	// [hook.StopPropagation] is returned in one of its listeners.
 | 	// [hook.StopPropagation] is returned in one of its listeners.
 | ||||||
| 	OnMailerBeforeRecordChangeEmailSend() *hook.Hook[*MailerRecordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnMailerBeforeRecordChangeEmailSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnMailerAfterRecordChangeEmailSend hook is triggered after a
 | 	// OnMailerAfterRecordChangeEmailSend hook is triggered after a
 | ||||||
| 	// verification email was successfully sent to an auth record.
 | 	// verification email was successfully sent to an auth record.
 | ||||||
| 	OnMailerAfterRecordChangeEmailSend() *hook.Hook[*MailerRecordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnMailerAfterRecordChangeEmailSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] | ||||||
| 
 | 
 | ||||||
| 	// ---------------------------------------------------------------
 | 	// ---------------------------------------------------------------
 | ||||||
| 	// Realtime API event hooks
 | 	// Realtime API event hooks
 | ||||||
|  | @ -257,7 +299,7 @@ type App interface { | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to validate or modify the file response before
 | 	// Could be used to validate or modify the file response before
 | ||||||
| 	// returning it to the client.
 | 	// returning it to the client.
 | ||||||
| 	OnFileDownloadRequest() *hook.Hook[*FileDownloadEvent] | 	OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*FileDownloadEvent] | ||||||
| 
 | 
 | ||||||
| 	// ---------------------------------------------------------------
 | 	// ---------------------------------------------------------------
 | ||||||
| 	// Admin API event hooks
 | 	// Admin API event hooks
 | ||||||
|  | @ -366,18 +408,27 @@ type App interface { | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate or modify the authenticated
 | 	// Could be used to additionally validate or modify the authenticated
 | ||||||
| 	// record data and token.
 | 	// record data and token.
 | ||||||
| 	OnRecordAuthRequest() *hook.Hook[*RecordAuthEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAuthRequest(tags ...string) *hook.TaggedHook[*RecordAuthEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeAuthWithPasswordRequest hook is triggered before each Record
 | 	// OnRecordBeforeAuthWithPasswordRequest hook is triggered before each Record
 | ||||||
| 	// auth with password API request (after request data load and before password validation).
 | 	// auth with password API request (after request data load and before password validation).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to implement for example a custom password validation
 | 	// Could be used to implement for example a custom password validation
 | ||||||
| 	// or to locate a different Record identity (by assigning [RecordAuthWithPasswordEvent.Record]).
 | 	// or to locate a different Record identity (by assigning [RecordAuthWithPasswordEvent.Record]).
 | ||||||
| 	OnRecordBeforeAuthWithPasswordRequest() *hook.Hook[*RecordAuthWithPasswordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeAuthWithPasswordRequest(tags ...string) *hook.TaggedHook[*RecordAuthWithPasswordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterAuthWithPasswordRequest hook is triggered after each
 | 	// OnRecordAfterAuthWithPasswordRequest hook is triggered after each
 | ||||||
| 	// successful Record auth with password API request.
 | 	// successful Record auth with password API request.
 | ||||||
| 	OnRecordAfterAuthWithPasswordRequest() *hook.Hook[*RecordAuthWithPasswordEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterAuthWithPasswordRequest(tags ...string) *hook.TaggedHook[*RecordAuthWithPasswordEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeAuthWithOAuth2Request hook is triggered before each Record
 | 	// OnRecordBeforeAuthWithOAuth2Request hook is triggered before each Record
 | ||||||
| 	// OAuth2 sign-in/sign-up API request (after token exchange and before external provider linking).
 | 	// OAuth2 sign-in/sign-up API request (after token exchange and before external provider linking).
 | ||||||
|  | @ -387,104 +438,161 @@ type App interface { | ||||||
| 	//
 | 	//
 | ||||||
| 	// To assign or link a different existing record model you can
 | 	// To assign or link a different existing record model you can
 | ||||||
| 	// overwrite/modify the [RecordAuthWithOAuth2Event.Record] field.
 | 	// overwrite/modify the [RecordAuthWithOAuth2Event.Record] field.
 | ||||||
| 	OnRecordBeforeAuthWithOAuth2Request() *hook.Hook[*RecordAuthWithOAuth2Event] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeAuthWithOAuth2Request(tags ...string) *hook.TaggedHook[*RecordAuthWithOAuth2Event] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterAuthWithOAuth2Request hook is triggered after each
 | 	// OnRecordAfterAuthWithOAuth2Request hook is triggered after each
 | ||||||
| 	// successful Record OAuth2 API request.
 | 	// successful Record OAuth2 API request.
 | ||||||
| 	OnRecordAfterAuthWithOAuth2Request() *hook.Hook[*RecordAuthWithOAuth2Event] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterAuthWithOAuth2Request(tags ...string) *hook.TaggedHook[*RecordAuthWithOAuth2Event] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeAuthRefreshRequest hook is triggered before each Record
 | 	// OnRecordBeforeAuthRefreshRequest hook is triggered before each Record
 | ||||||
| 	// auth refresh API request (right before generating a new auth token).
 | 	// auth refresh API request (right before generating a new auth token).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different auth refresh behavior (returning [hook.StopPropagation]).
 | 	// completely different auth refresh behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeAuthRefreshRequest() *hook.Hook[*RecordAuthRefreshEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeAuthRefreshRequest(tags ...string) *hook.TaggedHook[*RecordAuthRefreshEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterAuthRefreshRequest hook is triggered after each
 | 	// OnRecordAfterAuthRefreshRequest hook is triggered after each
 | ||||||
| 	// successful auth refresh API request (right after generating a new auth token).
 | 	// successful auth refresh API request (right after generating a new auth token).
 | ||||||
| 	OnRecordAfterAuthRefreshRequest() *hook.Hook[*RecordAuthRefreshEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterAuthRefreshRequest(tags ...string) *hook.TaggedHook[*RecordAuthRefreshEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeRequestPasswordResetRequest hook is triggered before each Record
 | 	// OnRecordBeforeRequestPasswordResetRequest hook is triggered before each Record
 | ||||||
| 	// request password reset API request (after request data load and before sending the reset email).
 | 	// request password reset API request (after request data load and before sending the reset email).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different password reset behavior (returning [hook.StopPropagation]).
 | 	// completely different password reset behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeRequestPasswordResetRequest() *hook.Hook[*RecordRequestPasswordResetEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeRequestPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordRequestPasswordResetEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterRequestPasswordResetRequest hook is triggered after each
 | 	// OnRecordAfterRequestPasswordResetRequest hook is triggered after each
 | ||||||
| 	// successful request password reset API request.
 | 	// successful request password reset API request.
 | ||||||
| 	OnRecordAfterRequestPasswordResetRequest() *hook.Hook[*RecordRequestPasswordResetEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterRequestPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordRequestPasswordResetEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeConfirmPasswordResetRequest hook is triggered before each Record
 | 	// OnRecordBeforeConfirmPasswordResetRequest hook is triggered before each Record
 | ||||||
| 	// confirm password reset API request (after request data load and before persistence).
 | 	// confirm password reset API request (after request data load and before persistence).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeConfirmPasswordResetRequest() *hook.Hook[*RecordConfirmPasswordResetEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeConfirmPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordConfirmPasswordResetEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterConfirmPasswordResetRequest hook is triggered after each
 | 	// OnRecordAfterConfirmPasswordResetRequest hook is triggered after each
 | ||||||
| 	// successful confirm password reset API request.
 | 	// successful confirm password reset API request.
 | ||||||
| 	OnRecordAfterConfirmPasswordResetRequest() *hook.Hook[*RecordConfirmPasswordResetEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterConfirmPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordConfirmPasswordResetEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeRequestVerificationRequest hook is triggered before each Record
 | 	// OnRecordBeforeRequestVerificationRequest hook is triggered before each Record
 | ||||||
| 	// request verification API request (after request data load and before sending the verification email).
 | 	// request verification API request (after request data load and before sending the verification email).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the loaded request data or implement
 | 	// Could be used to additionally validate the loaded request data or implement
 | ||||||
| 	// completely different verification behavior (returning [hook.StopPropagation]).
 | 	// completely different verification behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeRequestVerificationRequest() *hook.Hook[*RecordRequestVerificationEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeRequestVerificationRequest(tags ...string) *hook.TaggedHook[*RecordRequestVerificationEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterRequestVerificationRequest hook is triggered after each
 | 	// OnRecordAfterRequestVerificationRequest hook is triggered after each
 | ||||||
| 	// successful request verification API request.
 | 	// successful request verification API request.
 | ||||||
| 	OnRecordAfterRequestVerificationRequest() *hook.Hook[*RecordRequestVerificationEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterRequestVerificationRequest(tags ...string) *hook.TaggedHook[*RecordRequestVerificationEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeConfirmVerificationRequest hook is triggered before each Record
 | 	// OnRecordBeforeConfirmVerificationRequest hook is triggered before each Record
 | ||||||
| 	// confirm verification API request (after request data load and before persistence).
 | 	// confirm verification API request (after request data load and before persistence).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeConfirmVerificationRequest() *hook.Hook[*RecordConfirmVerificationEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeConfirmVerificationRequest(tags ...string) *hook.TaggedHook[*RecordConfirmVerificationEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterConfirmVerificationRequest hook is triggered after each
 | 	// OnRecordAfterConfirmVerificationRequest hook is triggered after each
 | ||||||
| 	// successful confirm verification API request.
 | 	// successful confirm verification API request.
 | ||||||
| 	OnRecordAfterConfirmVerificationRequest() *hook.Hook[*RecordConfirmVerificationEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterConfirmVerificationRequest(tags ...string) *hook.TaggedHook[*RecordConfirmVerificationEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeRequestEmailChangeRequest hook is triggered before each Record request email change API request
 | 	// OnRecordBeforeRequestEmailChangeRequest hook is triggered before each Record request email change API request
 | ||||||
| 	// (after request data load and before sending the email link to confirm the change).
 | 	// (after request data load and before sending the email link to confirm the change).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different request email change behavior (returning [hook.StopPropagation]).
 | 	// completely different request email change behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeRequestEmailChangeRequest() *hook.Hook[*RecordRequestEmailChangeEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeRequestEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordRequestEmailChangeEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterRequestEmailChangeRequest hook is triggered after each
 | 	// OnRecordAfterRequestEmailChangeRequest hook is triggered after each
 | ||||||
| 	// successful request email change API request.
 | 	// successful request email change API request.
 | ||||||
| 	OnRecordAfterRequestEmailChangeRequest() *hook.Hook[*RecordRequestEmailChangeEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterRequestEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordRequestEmailChangeEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeConfirmEmailChangeRequest hook is triggered before each Record
 | 	// OnRecordBeforeConfirmEmailChangeRequest hook is triggered before each Record
 | ||||||
| 	// confirm email change API request (after request data load and before persistence).
 | 	// confirm email change API request (after request data load and before persistence).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeConfirmEmailChangeRequest() *hook.Hook[*RecordConfirmEmailChangeEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeConfirmEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordConfirmEmailChangeEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterConfirmEmailChangeRequest hook is triggered after each
 | 	// OnRecordAfterConfirmEmailChangeRequest hook is triggered after each
 | ||||||
| 	// successful confirm email change API request.
 | 	// successful confirm email change API request.
 | ||||||
| 	OnRecordAfterConfirmEmailChangeRequest() *hook.Hook[*RecordConfirmEmailChangeEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterConfirmEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordConfirmEmailChangeEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordListExternalAuthsRequest hook is triggered on each API record external auths list request.
 | 	// OnRecordListExternalAuthsRequest hook is triggered on each API record external auths list request.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to validate or modify the response before returning it to the client.
 | 	// Could be used to validate or modify the response before returning it to the client.
 | ||||||
| 	OnRecordListExternalAuthsRequest() *hook.Hook[*RecordListExternalAuthsEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordListExternalAuthsRequest(tags ...string) *hook.TaggedHook[*RecordListExternalAuthsEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeUnlinkExternalAuthRequest hook is triggered before each API record
 | 	// OnRecordBeforeUnlinkExternalAuthRequest hook is triggered before each API record
 | ||||||
| 	// external auth unlink request (after models load and before the actual relation deletion).
 | 	// external auth unlink request (after models load and before the actual relation deletion).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different delete behavior (returning [hook.StopPropagation]).
 | 	// completely different delete behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeUnlinkExternalAuthRequest() *hook.Hook[*RecordUnlinkExternalAuthEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeUnlinkExternalAuthRequest(tags ...string) *hook.TaggedHook[*RecordUnlinkExternalAuthEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterUnlinkExternalAuthRequest hook is triggered after each
 | 	// OnRecordAfterUnlinkExternalAuthRequest hook is triggered after each
 | ||||||
| 	// successful API record external auth unlink request.
 | 	// successful API record external auth unlink request.
 | ||||||
| 	OnRecordAfterUnlinkExternalAuthRequest() *hook.Hook[*RecordUnlinkExternalAuthEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterUnlinkExternalAuthRequest(tags ...string) *hook.TaggedHook[*RecordUnlinkExternalAuthEvent] | ||||||
| 
 | 
 | ||||||
| 	// ---------------------------------------------------------------
 | 	// ---------------------------------------------------------------
 | ||||||
| 	// Record CRUD API event hooks
 | 	// Record CRUD API event hooks
 | ||||||
|  | @ -493,45 +601,69 @@ type App interface { | ||||||
| 	// OnRecordsListRequest hook is triggered on each API Records list request.
 | 	// OnRecordsListRequest hook is triggered on each API Records list request.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to validate or modify the response before returning it to the client.
 | 	// Could be used to validate or modify the response before returning it to the client.
 | ||||||
| 	OnRecordsListRequest() *hook.Hook[*RecordsListEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordsListRequest(tags ...string) *hook.TaggedHook[*RecordsListEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordViewRequest hook is triggered on each API Record view request.
 | 	// OnRecordViewRequest hook is triggered on each API Record view request.
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to validate or modify the response before returning it to the client.
 | 	// Could be used to validate or modify the response before returning it to the client.
 | ||||||
| 	OnRecordViewRequest() *hook.Hook[*RecordViewEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordViewRequest(tags ...string) *hook.TaggedHook[*RecordViewEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeCreateRequest hook is triggered before each API Record
 | 	// OnRecordBeforeCreateRequest hook is triggered before each API Record
 | ||||||
| 	// create request (after request data load and before model persistence).
 | 	// create request (after request data load and before model persistence).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeCreateRequest() *hook.Hook[*RecordCreateEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeCreateRequest(tags ...string) *hook.TaggedHook[*RecordCreateEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterCreateRequest hook is triggered after each
 | 	// OnRecordAfterCreateRequest hook is triggered after each
 | ||||||
| 	// successful API Record create request.
 | 	// successful API Record create request.
 | ||||||
| 	OnRecordAfterCreateRequest() *hook.Hook[*RecordCreateEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterCreateRequest(tags ...string) *hook.TaggedHook[*RecordCreateEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeUpdateRequest hook is triggered before each API Record
 | 	// OnRecordBeforeUpdateRequest hook is triggered before each API Record
 | ||||||
| 	// update request (after request data load and before model persistence).
 | 	// update request (after request data load and before model persistence).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | 	// completely different persistence behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeUpdateRequest() *hook.Hook[*RecordUpdateEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeUpdateRequest(tags ...string) *hook.TaggedHook[*RecordUpdateEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterUpdateRequest hook is triggered after each
 | 	// OnRecordAfterUpdateRequest hook is triggered after each
 | ||||||
| 	// successful API Record update request.
 | 	// successful API Record update request.
 | ||||||
| 	OnRecordAfterUpdateRequest() *hook.Hook[*RecordUpdateEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterUpdateRequest(tags ...string) *hook.TaggedHook[*RecordUpdateEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordBeforeDeleteRequest hook is triggered before each API Record
 | 	// OnRecordBeforeDeleteRequest hook is triggered before each API Record
 | ||||||
| 	// delete request (after model load and before actual deletion).
 | 	// delete request (after model load and before actual deletion).
 | ||||||
| 	//
 | 	//
 | ||||||
| 	// Could be used to additionally validate the request data or implement
 | 	// Could be used to additionally validate the request data or implement
 | ||||||
| 	// completely different delete behavior (returning [hook.StopPropagation]).
 | 	// completely different delete behavior (returning [hook.StopPropagation]).
 | ||||||
| 	OnRecordBeforeDeleteRequest() *hook.Hook[*RecordDeleteEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordBeforeDeleteRequest(tags ...string) *hook.TaggedHook[*RecordDeleteEvent] | ||||||
| 
 | 
 | ||||||
| 	// OnRecordAfterDeleteRequest hook is triggered after each
 | 	// OnRecordAfterDeleteRequest hook is triggered after each
 | ||||||
| 	// successful API Record delete request.
 | 	// successful API Record delete request.
 | ||||||
| 	OnRecordAfterDeleteRequest() *hook.Hook[*RecordDeleteEvent] | 	//
 | ||||||
|  | 	// You can optionally specify a list of "tags" (Collection ids or names)
 | ||||||
|  | 	// to filter any newly attached event data handler.
 | ||||||
|  | 	OnRecordAfterDeleteRequest(tags ...string) *hook.TaggedHook[*RecordDeleteEvent] | ||||||
| 
 | 
 | ||||||
| 	// ---------------------------------------------------------------
 | 	// ---------------------------------------------------------------
 | ||||||
| 	// Collection API event hooks
 | 	// Collection API event hooks
 | ||||||
|  |  | ||||||
							
								
								
									
										172
									
								
								core/base.go
								
								
								
								
							
							
						
						
									
										172
									
								
								core/base.go
								
								
								
								
							|  | @ -541,28 +541,28 @@ func (app *BaseApp) OnAfterApiError() *hook.Hook[*ApiErrorEvent] { | ||||||
| // Dao event hooks
 | // Dao event hooks
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnModelBeforeCreate() *hook.Hook[*ModelEvent] { | func (app *BaseApp) OnModelBeforeCreate(tags ...string) *hook.TaggedHook[*ModelEvent] { | ||||||
| 	return app.onModelBeforeCreate | 	return hook.NewTaggedHook(app.onModelBeforeCreate, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnModelAfterCreate() *hook.Hook[*ModelEvent] { | func (app *BaseApp) OnModelAfterCreate(tags ...string) *hook.TaggedHook[*ModelEvent] { | ||||||
| 	return app.onModelAfterCreate | 	return hook.NewTaggedHook(app.onModelAfterCreate, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnModelBeforeUpdate() *hook.Hook[*ModelEvent] { | func (app *BaseApp) OnModelBeforeUpdate(tags ...string) *hook.TaggedHook[*ModelEvent] { | ||||||
| 	return app.onModelBeforeUpdate | 	return hook.NewTaggedHook(app.onModelBeforeUpdate, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnModelAfterUpdate() *hook.Hook[*ModelEvent] { | func (app *BaseApp) OnModelAfterUpdate(tags ...string) *hook.TaggedHook[*ModelEvent] { | ||||||
| 	return app.onModelAfterUpdate | 	return hook.NewTaggedHook(app.onModelAfterUpdate, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnModelBeforeDelete() *hook.Hook[*ModelEvent] { | func (app *BaseApp) OnModelBeforeDelete(tags ...string) *hook.TaggedHook[*ModelEvent] { | ||||||
| 	return app.onModelBeforeDelete | 	return hook.NewTaggedHook(app.onModelBeforeDelete, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnModelAfterDelete() *hook.Hook[*ModelEvent] { | func (app *BaseApp) OnModelAfterDelete(tags ...string) *hook.TaggedHook[*ModelEvent] { | ||||||
| 	return app.onModelAfterDelete | 	return hook.NewTaggedHook(app.onModelAfterDelete, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
|  | @ -577,28 +577,28 @@ func (app *BaseApp) OnMailerAfterAdminResetPasswordSend() *hook.Hook[*MailerAdmi | ||||||
| 	return app.onMailerAfterAdminResetPasswordSend | 	return app.onMailerAfterAdminResetPasswordSend | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnMailerBeforeRecordResetPasswordSend() *hook.Hook[*MailerRecordEvent] { | func (app *BaseApp) OnMailerBeforeRecordResetPasswordSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { | ||||||
| 	return app.onMailerBeforeRecordResetPasswordSend | 	return hook.NewTaggedHook(app.onMailerBeforeRecordResetPasswordSend, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnMailerAfterRecordResetPasswordSend() *hook.Hook[*MailerRecordEvent] { | func (app *BaseApp) OnMailerAfterRecordResetPasswordSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { | ||||||
| 	return app.onMailerAfterRecordResetPasswordSend | 	return hook.NewTaggedHook(app.onMailerAfterRecordResetPasswordSend, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnMailerBeforeRecordVerificationSend() *hook.Hook[*MailerRecordEvent] { | func (app *BaseApp) OnMailerBeforeRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { | ||||||
| 	return app.onMailerBeforeRecordVerificationSend | 	return hook.NewTaggedHook(app.onMailerBeforeRecordVerificationSend, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnMailerAfterRecordVerificationSend() *hook.Hook[*MailerRecordEvent] { | func (app *BaseApp) OnMailerAfterRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { | ||||||
| 	return app.onMailerAfterRecordVerificationSend | 	return hook.NewTaggedHook(app.onMailerAfterRecordVerificationSend, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnMailerBeforeRecordChangeEmailSend() *hook.Hook[*MailerRecordEvent] { | func (app *BaseApp) OnMailerBeforeRecordChangeEmailSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { | ||||||
| 	return app.onMailerBeforeRecordChangeEmailSend | 	return hook.NewTaggedHook(app.onMailerBeforeRecordChangeEmailSend, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnMailerAfterRecordChangeEmailSend() *hook.Hook[*MailerRecordEvent] { | func (app *BaseApp) OnMailerAfterRecordChangeEmailSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { | ||||||
| 	return app.onMailerAfterRecordChangeEmailSend | 	return hook.NewTaggedHook(app.onMailerAfterRecordChangeEmailSend, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
|  | @ -649,8 +649,8 @@ func (app *BaseApp) OnSettingsAfterUpdateRequest() *hook.Hook[*SettingsUpdateEve | ||||||
| // File API event hooks
 | // File API event hooks
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnFileDownloadRequest() *hook.Hook[*FileDownloadEvent] { | func (app *BaseApp) OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*FileDownloadEvent] { | ||||||
| 	return app.onFileDownloadRequest | 	return hook.NewTaggedHook(app.onFileDownloadRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
|  | @ -729,128 +729,128 @@ func (app *BaseApp) OnAdminAfterConfirmPasswordResetRequest() *hook.Hook[*AdminC | ||||||
| // Record auth API event hooks
 | // Record auth API event hooks
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAuthRequest() *hook.Hook[*RecordAuthEvent] { | func (app *BaseApp) OnRecordAuthRequest(tags ...string) *hook.TaggedHook[*RecordAuthEvent] { | ||||||
| 	return app.onRecordAuthRequest | 	return hook.NewTaggedHook(app.onRecordAuthRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeAuthWithPasswordRequest() *hook.Hook[*RecordAuthWithPasswordEvent] { | func (app *BaseApp) OnRecordBeforeAuthWithPasswordRequest(tags ...string) *hook.TaggedHook[*RecordAuthWithPasswordEvent] { | ||||||
| 	return app.onRecordBeforeAuthWithPasswordRequest | 	return hook.NewTaggedHook(app.onRecordBeforeAuthWithPasswordRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterAuthWithPasswordRequest() *hook.Hook[*RecordAuthWithPasswordEvent] { | func (app *BaseApp) OnRecordAfterAuthWithPasswordRequest(tags ...string) *hook.TaggedHook[*RecordAuthWithPasswordEvent] { | ||||||
| 	return app.onRecordAfterAuthWithPasswordRequest | 	return hook.NewTaggedHook(app.onRecordAfterAuthWithPasswordRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeAuthWithOAuth2Request() *hook.Hook[*RecordAuthWithOAuth2Event] { | func (app *BaseApp) OnRecordBeforeAuthWithOAuth2Request(tags ...string) *hook.TaggedHook[*RecordAuthWithOAuth2Event] { | ||||||
| 	return app.onRecordBeforeAuthWithOAuth2Request | 	return hook.NewTaggedHook(app.onRecordBeforeAuthWithOAuth2Request, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterAuthWithOAuth2Request() *hook.Hook[*RecordAuthWithOAuth2Event] { | func (app *BaseApp) OnRecordAfterAuthWithOAuth2Request(tags ...string) *hook.TaggedHook[*RecordAuthWithOAuth2Event] { | ||||||
| 	return app.onRecordAfterAuthWithOAuth2Request | 	return hook.NewTaggedHook(app.onRecordAfterAuthWithOAuth2Request, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeAuthRefreshRequest() *hook.Hook[*RecordAuthRefreshEvent] { | func (app *BaseApp) OnRecordBeforeAuthRefreshRequest(tags ...string) *hook.TaggedHook[*RecordAuthRefreshEvent] { | ||||||
| 	return app.onRecordBeforeAuthRefreshRequest | 	return hook.NewTaggedHook(app.onRecordBeforeAuthRefreshRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterAuthRefreshRequest() *hook.Hook[*RecordAuthRefreshEvent] { | func (app *BaseApp) OnRecordAfterAuthRefreshRequest(tags ...string) *hook.TaggedHook[*RecordAuthRefreshEvent] { | ||||||
| 	return app.onRecordAfterAuthRefreshRequest | 	return hook.NewTaggedHook(app.onRecordAfterAuthRefreshRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeRequestPasswordResetRequest() *hook.Hook[*RecordRequestPasswordResetEvent] { | func (app *BaseApp) OnRecordBeforeRequestPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordRequestPasswordResetEvent] { | ||||||
| 	return app.onRecordBeforeRequestPasswordResetRequest | 	return hook.NewTaggedHook(app.onRecordBeforeRequestPasswordResetRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterRequestPasswordResetRequest() *hook.Hook[*RecordRequestPasswordResetEvent] { | func (app *BaseApp) OnRecordAfterRequestPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordRequestPasswordResetEvent] { | ||||||
| 	return app.onRecordAfterRequestPasswordResetRequest | 	return hook.NewTaggedHook(app.onRecordAfterRequestPasswordResetRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeConfirmPasswordResetRequest() *hook.Hook[*RecordConfirmPasswordResetEvent] { | func (app *BaseApp) OnRecordBeforeConfirmPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordConfirmPasswordResetEvent] { | ||||||
| 	return app.onRecordBeforeConfirmPasswordResetRequest | 	return hook.NewTaggedHook(app.onRecordBeforeConfirmPasswordResetRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterConfirmPasswordResetRequest() *hook.Hook[*RecordConfirmPasswordResetEvent] { | func (app *BaseApp) OnRecordAfterConfirmPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordConfirmPasswordResetEvent] { | ||||||
| 	return app.onRecordAfterConfirmPasswordResetRequest | 	return hook.NewTaggedHook(app.onRecordAfterConfirmPasswordResetRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeRequestVerificationRequest() *hook.Hook[*RecordRequestVerificationEvent] { | func (app *BaseApp) OnRecordBeforeRequestVerificationRequest(tags ...string) *hook.TaggedHook[*RecordRequestVerificationEvent] { | ||||||
| 	return app.onRecordBeforeRequestVerificationRequest | 	return hook.NewTaggedHook(app.onRecordBeforeRequestVerificationRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterRequestVerificationRequest() *hook.Hook[*RecordRequestVerificationEvent] { | func (app *BaseApp) OnRecordAfterRequestVerificationRequest(tags ...string) *hook.TaggedHook[*RecordRequestVerificationEvent] { | ||||||
| 	return app.onRecordAfterRequestVerificationRequest | 	return hook.NewTaggedHook(app.onRecordAfterRequestVerificationRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeConfirmVerificationRequest() *hook.Hook[*RecordConfirmVerificationEvent] { | func (app *BaseApp) OnRecordBeforeConfirmVerificationRequest(tags ...string) *hook.TaggedHook[*RecordConfirmVerificationEvent] { | ||||||
| 	return app.onRecordBeforeConfirmVerificationRequest | 	return hook.NewTaggedHook(app.onRecordBeforeConfirmVerificationRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterConfirmVerificationRequest() *hook.Hook[*RecordConfirmVerificationEvent] { | func (app *BaseApp) OnRecordAfterConfirmVerificationRequest(tags ...string) *hook.TaggedHook[*RecordConfirmVerificationEvent] { | ||||||
| 	return app.onRecordAfterConfirmVerificationRequest | 	return hook.NewTaggedHook(app.onRecordAfterConfirmVerificationRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeRequestEmailChangeRequest() *hook.Hook[*RecordRequestEmailChangeEvent] { | func (app *BaseApp) OnRecordBeforeRequestEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordRequestEmailChangeEvent] { | ||||||
| 	return app.onRecordBeforeRequestEmailChangeRequest | 	return hook.NewTaggedHook(app.onRecordBeforeRequestEmailChangeRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterRequestEmailChangeRequest() *hook.Hook[*RecordRequestEmailChangeEvent] { | func (app *BaseApp) OnRecordAfterRequestEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordRequestEmailChangeEvent] { | ||||||
| 	return app.onRecordAfterRequestEmailChangeRequest | 	return hook.NewTaggedHook(app.onRecordAfterRequestEmailChangeRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeConfirmEmailChangeRequest() *hook.Hook[*RecordConfirmEmailChangeEvent] { | func (app *BaseApp) OnRecordBeforeConfirmEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordConfirmEmailChangeEvent] { | ||||||
| 	return app.onRecordBeforeConfirmEmailChangeRequest | 	return hook.NewTaggedHook(app.onRecordBeforeConfirmEmailChangeRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterConfirmEmailChangeRequest() *hook.Hook[*RecordConfirmEmailChangeEvent] { | func (app *BaseApp) OnRecordAfterConfirmEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordConfirmEmailChangeEvent] { | ||||||
| 	return app.onRecordAfterConfirmEmailChangeRequest | 	return hook.NewTaggedHook(app.onRecordAfterConfirmEmailChangeRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordListExternalAuthsRequest() *hook.Hook[*RecordListExternalAuthsEvent] { | func (app *BaseApp) OnRecordListExternalAuthsRequest(tags ...string) *hook.TaggedHook[*RecordListExternalAuthsEvent] { | ||||||
| 	return app.onRecordListExternalAuthsRequest | 	return hook.NewTaggedHook(app.onRecordListExternalAuthsRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeUnlinkExternalAuthRequest() *hook.Hook[*RecordUnlinkExternalAuthEvent] { | func (app *BaseApp) OnRecordBeforeUnlinkExternalAuthRequest(tags ...string) *hook.TaggedHook[*RecordUnlinkExternalAuthEvent] { | ||||||
| 	return app.onRecordBeforeUnlinkExternalAuthRequest | 	return hook.NewTaggedHook(app.onRecordBeforeUnlinkExternalAuthRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterUnlinkExternalAuthRequest() *hook.Hook[*RecordUnlinkExternalAuthEvent] { | func (app *BaseApp) OnRecordAfterUnlinkExternalAuthRequest(tags ...string) *hook.TaggedHook[*RecordUnlinkExternalAuthEvent] { | ||||||
| 	return app.onRecordAfterUnlinkExternalAuthRequest | 	return hook.NewTaggedHook(app.onRecordAfterUnlinkExternalAuthRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| // Record CRUD API event hooks
 | // Record CRUD API event hooks
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordsListRequest() *hook.Hook[*RecordsListEvent] { | func (app *BaseApp) OnRecordsListRequest(tags ...string) *hook.TaggedHook[*RecordsListEvent] { | ||||||
| 	return app.onRecordsListRequest | 	return hook.NewTaggedHook(app.onRecordsListRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordViewRequest() *hook.Hook[*RecordViewEvent] { | func (app *BaseApp) OnRecordViewRequest(tags ...string) *hook.TaggedHook[*RecordViewEvent] { | ||||||
| 	return app.onRecordViewRequest | 	return hook.NewTaggedHook(app.onRecordViewRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeCreateRequest() *hook.Hook[*RecordCreateEvent] { | func (app *BaseApp) OnRecordBeforeCreateRequest(tags ...string) *hook.TaggedHook[*RecordCreateEvent] { | ||||||
| 	return app.onRecordBeforeCreateRequest | 	return hook.NewTaggedHook(app.onRecordBeforeCreateRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterCreateRequest() *hook.Hook[*RecordCreateEvent] { | func (app *BaseApp) OnRecordAfterCreateRequest(tags ...string) *hook.TaggedHook[*RecordCreateEvent] { | ||||||
| 	return app.onRecordAfterCreateRequest | 	return hook.NewTaggedHook(app.onRecordAfterCreateRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeUpdateRequest() *hook.Hook[*RecordUpdateEvent] { | func (app *BaseApp) OnRecordBeforeUpdateRequest(tags ...string) *hook.TaggedHook[*RecordUpdateEvent] { | ||||||
| 	return app.onRecordBeforeUpdateRequest | 	return hook.NewTaggedHook(app.onRecordBeforeUpdateRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterUpdateRequest() *hook.Hook[*RecordUpdateEvent] { | func (app *BaseApp) OnRecordAfterUpdateRequest(tags ...string) *hook.TaggedHook[*RecordUpdateEvent] { | ||||||
| 	return app.onRecordAfterUpdateRequest | 	return hook.NewTaggedHook(app.onRecordAfterUpdateRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordBeforeDeleteRequest() *hook.Hook[*RecordDeleteEvent] { | func (app *BaseApp) OnRecordBeforeDeleteRequest(tags ...string) *hook.TaggedHook[*RecordDeleteEvent] { | ||||||
| 	return app.onRecordBeforeDeleteRequest | 	return hook.NewTaggedHook(app.onRecordBeforeDeleteRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (app *BaseApp) OnRecordAfterDeleteRequest() *hook.Hook[*RecordDeleteEvent] { | func (app *BaseApp) OnRecordAfterDeleteRequest(tags ...string) *hook.TaggedHook[*RecordDeleteEvent] { | ||||||
| 	return app.onRecordAfterDeleteRequest | 	return hook.NewTaggedHook(app.onRecordAfterDeleteRequest, tags...) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
|  |  | ||||||
|  | @ -182,238 +182,6 @@ func TestBaseAppGetters(t *testing.T) { | ||||||
| 	if app.onBeforeServe != app.OnBeforeServe() || app.OnBeforeServe() == nil { | 	if app.onBeforeServe != app.OnBeforeServe() || app.OnBeforeServe() == nil { | ||||||
| 		t.Fatalf("Getter app.OnBeforeServe does not match or nil (%v vs %v)", app.OnBeforeServe(), app.onBeforeServe) | 		t.Fatalf("Getter app.OnBeforeServe does not match or nil (%v vs %v)", app.OnBeforeServe(), app.onBeforeServe) | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	if app.onModelBeforeCreate != app.OnModelBeforeCreate() || app.OnModelBeforeCreate() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnModelBeforeCreate does not match or nil (%v vs %v)", app.OnModelBeforeCreate(), app.onModelBeforeCreate) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onModelAfterCreate != app.OnModelAfterCreate() || app.OnModelAfterCreate() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnModelAfterCreate does not match or nil (%v vs %v)", app.OnModelAfterCreate(), app.onModelAfterCreate) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onModelBeforeUpdate != app.OnModelBeforeUpdate() || app.OnModelBeforeUpdate() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnModelBeforeUpdate does not match or nil (%v vs %v)", app.OnModelBeforeUpdate(), app.onModelBeforeUpdate) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onModelAfterUpdate != app.OnModelAfterUpdate() || app.OnModelAfterUpdate() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnModelAfterUpdate does not match or nil (%v vs %v)", app.OnModelAfterUpdate(), app.onModelAfterUpdate) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onModelBeforeDelete != app.OnModelBeforeDelete() || app.OnModelBeforeDelete() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnModelBeforeDelete does not match or nil (%v vs %v)", app.OnModelBeforeDelete(), app.onModelBeforeDelete) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onModelAfterDelete != app.OnModelAfterDelete() || app.OnModelAfterDelete() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnModelAfterDelete does not match or nil (%v vs %v)", app.OnModelAfterDelete(), app.onModelAfterDelete) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerBeforeAdminResetPasswordSend != app.OnMailerBeforeAdminResetPasswordSend() || app.OnMailerBeforeAdminResetPasswordSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerBeforeAdminResetPasswordSend does not match or nil (%v vs %v)", app.OnMailerBeforeAdminResetPasswordSend(), app.onMailerBeforeAdminResetPasswordSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerAfterAdminResetPasswordSend != app.OnMailerAfterAdminResetPasswordSend() || app.OnMailerAfterAdminResetPasswordSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerAfterAdminResetPasswordSend does not match or nil (%v vs %v)", app.OnMailerAfterAdminResetPasswordSend(), app.onMailerAfterAdminResetPasswordSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerBeforeRecordResetPasswordSend != app.OnMailerBeforeRecordResetPasswordSend() || app.OnMailerBeforeRecordResetPasswordSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerBeforeRecordResetPasswordSend does not match or nil (%v vs %v)", app.OnMailerBeforeRecordResetPasswordSend(), app.onMailerBeforeRecordResetPasswordSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerAfterRecordResetPasswordSend != app.OnMailerAfterRecordResetPasswordSend() || app.OnMailerAfterRecordResetPasswordSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerAfterRecordResetPasswordSend does not match or nil (%v vs %v)", app.OnMailerAfterRecordResetPasswordSend(), app.onMailerAfterRecordResetPasswordSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerBeforeRecordVerificationSend != app.OnMailerBeforeRecordVerificationSend() || app.OnMailerBeforeRecordVerificationSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerBeforeRecordVerificationSend does not match or nil (%v vs %v)", app.OnMailerBeforeRecordVerificationSend(), app.onMailerBeforeRecordVerificationSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerAfterRecordVerificationSend != app.OnMailerAfterRecordVerificationSend() || app.OnMailerAfterRecordVerificationSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerAfterRecordVerificationSend does not match or nil (%v vs %v)", app.OnMailerAfterRecordVerificationSend(), app.onMailerAfterRecordVerificationSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerBeforeRecordChangeEmailSend != app.OnMailerBeforeRecordChangeEmailSend() || app.OnMailerBeforeRecordChangeEmailSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerBeforeRecordChangeEmailSend does not match or nil (%v vs %v)", app.OnMailerBeforeRecordChangeEmailSend(), app.onMailerBeforeRecordChangeEmailSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onMailerAfterRecordChangeEmailSend != app.OnMailerAfterRecordChangeEmailSend() || app.OnMailerAfterRecordChangeEmailSend() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnMailerAfterRecordChangeEmailSend does not match or nil (%v vs %v)", app.OnMailerAfterRecordChangeEmailSend(), app.onMailerAfterRecordChangeEmailSend) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRealtimeConnectRequest != app.OnRealtimeConnectRequest() || app.OnRealtimeConnectRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRealtimeConnectRequest does not match or nil (%v vs %v)", app.OnRealtimeConnectRequest(), app.onRealtimeConnectRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRealtimeBeforeSubscribeRequest != app.OnRealtimeBeforeSubscribeRequest() || app.OnRealtimeBeforeSubscribeRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRealtimeBeforeSubscribeRequest does not match or nil (%v vs %v)", app.OnRealtimeBeforeSubscribeRequest(), app.onRealtimeBeforeSubscribeRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRealtimeAfterSubscribeRequest != app.OnRealtimeAfterSubscribeRequest() || app.OnRealtimeAfterSubscribeRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRealtimeAfterSubscribeRequest does not match or nil (%v vs %v)", app.OnRealtimeAfterSubscribeRequest(), app.onRealtimeAfterSubscribeRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onSettingsListRequest != app.OnSettingsListRequest() || app.OnSettingsListRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnSettingsListRequest does not match or nil (%v vs %v)", app.OnSettingsListRequest(), app.onSettingsListRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onSettingsBeforeUpdateRequest != app.OnSettingsBeforeUpdateRequest() || app.OnSettingsBeforeUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnSettingsBeforeUpdateRequest does not match or nil (%v vs %v)", app.OnSettingsBeforeUpdateRequest(), app.onSettingsBeforeUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onSettingsAfterUpdateRequest != app.OnSettingsAfterUpdateRequest() || app.OnSettingsAfterUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnSettingsAfterUpdateRequest does not match or nil (%v vs %v)", app.OnSettingsAfterUpdateRequest(), app.onSettingsAfterUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onFileDownloadRequest != app.OnFileDownloadRequest() || app.OnFileDownloadRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnFileDownloadRequest does not match or nil (%v vs %v)", app.OnFileDownloadRequest(), app.onFileDownloadRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminsListRequest != app.OnAdminsListRequest() || app.OnAdminsListRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminsListRequest does not match or nil (%v vs %v)", app.OnAdminsListRequest(), app.onAdminsListRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminViewRequest != app.OnAdminViewRequest() || app.OnAdminViewRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminViewRequest does not match or nil (%v vs %v)", app.OnAdminViewRequest(), app.onAdminViewRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminBeforeCreateRequest != app.OnAdminBeforeCreateRequest() || app.OnAdminBeforeCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminBeforeCreateRequest does not match or nil (%v vs %v)", app.OnAdminBeforeCreateRequest(), app.onAdminBeforeCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminAfterCreateRequest != app.OnAdminAfterCreateRequest() || app.OnAdminAfterCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminAfterCreateRequest does not match or nil (%v vs %v)", app.OnAdminAfterCreateRequest(), app.onAdminAfterCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminBeforeUpdateRequest != app.OnAdminBeforeUpdateRequest() || app.OnAdminBeforeUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminBeforeUpdateRequest does not match or nil (%v vs %v)", app.OnAdminBeforeUpdateRequest(), app.onAdminBeforeUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminAfterUpdateRequest != app.OnAdminAfterUpdateRequest() || app.OnAdminAfterUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminAfterUpdateRequest does not match or nil (%v vs %v)", app.OnAdminAfterUpdateRequest(), app.onAdminAfterUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminBeforeDeleteRequest != app.OnAdminBeforeDeleteRequest() || app.OnAdminBeforeDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminBeforeDeleteRequest does not match or nil (%v vs %v)", app.OnAdminBeforeDeleteRequest(), app.onAdminBeforeDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminAfterDeleteRequest != app.OnAdminAfterDeleteRequest() || app.OnAdminAfterDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminAfterDeleteRequest does not match or nil (%v vs %v)", app.OnAdminAfterDeleteRequest(), app.onAdminAfterDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onAdminAuthRequest != app.OnAdminAuthRequest() || app.OnAdminAuthRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnAdminAuthRequest does not match or nil (%v vs %v)", app.OnAdminAuthRequest(), app.onAdminAuthRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordsListRequest != app.OnRecordsListRequest() || app.OnRecordsListRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordsListRequest does not match or nil (%v vs %v)", app.OnRecordsListRequest(), app.onRecordsListRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordViewRequest != app.OnRecordViewRequest() || app.OnRecordViewRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordViewRequest does not match or nil (%v vs %v)", app.OnRecordViewRequest(), app.onRecordViewRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeCreateRequest != app.OnRecordBeforeCreateRequest() || app.OnRecordBeforeCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeCreateRequest does not match or nil (%v vs %v)", app.OnRecordBeforeCreateRequest(), app.onRecordBeforeCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterCreateRequest != app.OnRecordAfterCreateRequest() || app.OnRecordAfterCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterCreateRequest does not match or nil (%v vs %v)", app.OnRecordAfterCreateRequest(), app.onRecordAfterCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeUpdateRequest != app.OnRecordBeforeUpdateRequest() || app.OnRecordBeforeUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeUpdateRequest does not match or nil (%v vs %v)", app.OnRecordBeforeUpdateRequest(), app.onRecordBeforeUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterUpdateRequest != app.OnRecordAfterUpdateRequest() || app.OnRecordAfterUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterUpdateRequest does not match or nil (%v vs %v)", app.OnRecordAfterUpdateRequest(), app.onRecordAfterUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeDeleteRequest != app.OnRecordBeforeDeleteRequest() || app.OnRecordBeforeDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeDeleteRequest does not match or nil (%v vs %v)", app.OnRecordBeforeDeleteRequest(), app.onRecordBeforeDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterDeleteRequest != app.OnRecordAfterDeleteRequest() || app.OnRecordAfterDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterDeleteRequest does not match or nil (%v vs %v)", app.OnRecordAfterDeleteRequest(), app.onRecordAfterDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAuthRequest != app.OnRecordAuthRequest() || app.OnRecordAuthRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAuthRequest does not match or nil (%v vs %v)", app.OnRecordAuthRequest(), app.onRecordAuthRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordListExternalAuthsRequest != app.OnRecordListExternalAuthsRequest() || app.OnRecordListExternalAuthsRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordListExternalAuthsRequest does not match or nil (%v vs %v)", app.OnRecordListExternalAuthsRequest(), app.onRecordListExternalAuthsRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeUnlinkExternalAuthRequest != app.OnRecordBeforeUnlinkExternalAuthRequest() || app.OnRecordBeforeUnlinkExternalAuthRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeUnlinkExternalAuthRequest does not match or nil (%v vs %v)", app.OnRecordBeforeUnlinkExternalAuthRequest(), app.onRecordBeforeUnlinkExternalAuthRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterUnlinkExternalAuthRequest != app.OnRecordAfterUnlinkExternalAuthRequest() || app.OnRecordAfterUnlinkExternalAuthRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterUnlinkExternalAuthRequest does not match or nil (%v vs %v)", app.OnRecordAfterUnlinkExternalAuthRequest(), app.onRecordAfterUnlinkExternalAuthRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordsListRequest != app.OnRecordsListRequest() || app.OnRecordsListRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordsListRequest does not match or nil (%v vs %v)", app.OnRecordsListRequest(), app.onRecordsListRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordViewRequest != app.OnRecordViewRequest() || app.OnRecordViewRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordViewRequest does not match or nil (%v vs %v)", app.OnRecordViewRequest(), app.onRecordViewRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeCreateRequest != app.OnRecordBeforeCreateRequest() || app.OnRecordBeforeCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeCreateRequest does not match or nil (%v vs %v)", app.OnRecordBeforeCreateRequest(), app.onRecordBeforeCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterCreateRequest != app.OnRecordAfterCreateRequest() || app.OnRecordAfterCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterCreateRequest does not match or nil (%v vs %v)", app.OnRecordAfterCreateRequest(), app.onRecordAfterCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeUpdateRequest != app.OnRecordBeforeUpdateRequest() || app.OnRecordBeforeUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeUpdateRequest does not match or nil (%v vs %v)", app.OnRecordBeforeUpdateRequest(), app.onRecordBeforeUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterUpdateRequest != app.OnRecordAfterUpdateRequest() || app.OnRecordAfterUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterUpdateRequest does not match or nil (%v vs %v)", app.OnRecordAfterUpdateRequest(), app.onRecordAfterUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordBeforeDeleteRequest != app.OnRecordBeforeDeleteRequest() || app.OnRecordBeforeDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordBeforeDeleteRequest does not match or nil (%v vs %v)", app.OnRecordBeforeDeleteRequest(), app.onRecordBeforeDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onRecordAfterDeleteRequest != app.OnRecordAfterDeleteRequest() || app.OnRecordAfterDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnRecordAfterDeleteRequest does not match or nil (%v vs %v)", app.OnRecordAfterDeleteRequest(), app.onRecordAfterDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionsListRequest != app.OnCollectionsListRequest() || app.OnCollectionsListRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionsListRequest does not match or nil (%v vs %v)", app.OnCollectionsListRequest(), app.onCollectionsListRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionViewRequest != app.OnCollectionViewRequest() || app.OnCollectionViewRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionViewRequest does not match or nil (%v vs %v)", app.OnCollectionViewRequest(), app.onCollectionViewRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionBeforeCreateRequest != app.OnCollectionBeforeCreateRequest() || app.OnCollectionBeforeCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionBeforeCreateRequest does not match or nil (%v vs %v)", app.OnCollectionBeforeCreateRequest(), app.onCollectionBeforeCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionAfterCreateRequest != app.OnCollectionAfterCreateRequest() || app.OnCollectionAfterCreateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionAfterCreateRequest does not match or nil (%v vs %v)", app.OnCollectionAfterCreateRequest(), app.onCollectionAfterCreateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionBeforeUpdateRequest != app.OnCollectionBeforeUpdateRequest() || app.OnCollectionBeforeUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionBeforeUpdateRequest does not match or nil (%v vs %v)", app.OnCollectionBeforeUpdateRequest(), app.onCollectionBeforeUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionAfterUpdateRequest != app.OnCollectionAfterUpdateRequest() || app.OnCollectionAfterUpdateRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionAfterUpdateRequest does not match or nil (%v vs %v)", app.OnCollectionAfterUpdateRequest(), app.onCollectionAfterUpdateRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionBeforeDeleteRequest != app.OnCollectionBeforeDeleteRequest() || app.OnCollectionBeforeDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionBeforeDeleteRequest does not match or nil (%v vs %v)", app.OnCollectionBeforeDeleteRequest(), app.onCollectionBeforeDeleteRequest) |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	if app.onCollectionAfterDeleteRequest != app.OnCollectionAfterDeleteRequest() || app.OnCollectionAfterDeleteRequest() == nil { |  | ||||||
| 		t.Fatalf("Getter app.OnCollectionAfterDeleteRequest does not match or nil (%v vs %v)", app.OnCollectionAfterDeleteRequest(), app.onCollectionAfterDeleteRequest) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestBaseAppNewMailClient(t *testing.T) { | func TestBaseAppNewMailClient(t *testing.T) { | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ func initPragmas(db *dbx.DB) error { | ||||||
| 	_, err := db.NewQuery(` | 	_, err := db.NewQuery(` | ||||||
| 		PRAGMA busy_timeout       = 10000; | 		PRAGMA busy_timeout       = 10000; | ||||||
| 		PRAGMA journal_mode       = WAL; | 		PRAGMA journal_mode       = WAL; | ||||||
| 		PRAGMA journal_size_limit = 100000000; | 		PRAGMA journal_size_limit = 200000000; | ||||||
| 		PRAGMA synchronous        = NORMAL; | 		PRAGMA synchronous        = NORMAL; | ||||||
| 		PRAGMA foreign_keys       = TRUE; | 		PRAGMA foreign_keys       = TRUE; | ||||||
| 	`).Execute() | 	`).Execute() | ||||||
|  |  | ||||||
|  | @ -6,6 +6,7 @@ import ( | ||||||
| 	"github.com/pocketbase/pocketbase/models/schema" | 	"github.com/pocketbase/pocketbase/models/schema" | ||||||
| 	"github.com/pocketbase/pocketbase/models/settings" | 	"github.com/pocketbase/pocketbase/models/settings" | ||||||
| 	"github.com/pocketbase/pocketbase/tools/auth" | 	"github.com/pocketbase/pocketbase/tools/auth" | ||||||
|  | 	"github.com/pocketbase/pocketbase/tools/hook" | ||||||
| 	"github.com/pocketbase/pocketbase/tools/mailer" | 	"github.com/pocketbase/pocketbase/tools/mailer" | ||||||
| 	"github.com/pocketbase/pocketbase/tools/search" | 	"github.com/pocketbase/pocketbase/tools/search" | ||||||
| 	"github.com/pocketbase/pocketbase/tools/subscriptions" | 	"github.com/pocketbase/pocketbase/tools/subscriptions" | ||||||
|  | @ -13,6 +14,28 @@ import ( | ||||||
| 	"github.com/labstack/echo/v5" | 	"github.com/labstack/echo/v5" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | type BaseCollectionEvent struct { | ||||||
|  | 	Collection *models.Collection | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *BaseCollectionEvent) Tags() []string { | ||||||
|  | 	if e.Collection == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tags := make([]string, 0, 2) | ||||||
|  | 
 | ||||||
|  | 	if e.Collection.Id != "" { | ||||||
|  | 		tags = append(tags, e.Collection.Id) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if e.Collection.Name != "" { | ||||||
|  | 		tags = append(tags, e.Collection.Name) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return tags | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| // Serve events data
 | // Serve events data
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
|  | @ -35,16 +58,32 @@ type ApiErrorEvent struct { | ||||||
| // Model DAO events data
 | // Model DAO events data
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
|  | var _ hook.Tagger = (*ModelEvent)(nil) | ||||||
|  | 
 | ||||||
| type ModelEvent struct { | type ModelEvent struct { | ||||||
| 	Dao   *daos.Dao | 	Dao   *daos.Dao | ||||||
| 	Model models.Model | 	Model models.Model | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | func (e *ModelEvent) Tags() []string { | ||||||
|  | 	if e.Model == nil { | ||||||
|  | 		return nil | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if r, ok := e.Model.(*models.Record); ok && r.Collection() != nil { | ||||||
|  | 		return []string{r.Collection().Id, r.Collection().Name} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return []string{e.Model.TableName()} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| // Mailer events data
 | // Mailer events data
 | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| type MailerRecordEvent struct { | type MailerRecordEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	MailClient mailer.Mailer | 	MailClient mailer.Mailer | ||||||
| 	Message    *mailer.Message | 	Message    *mailer.Message | ||||||
| 	Record     *models.Record | 	Record     *models.Record | ||||||
|  | @ -104,28 +143,37 @@ type SettingsUpdateEvent struct { | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| type RecordsListEvent struct { | type RecordsListEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Collection  *models.Collection |  | ||||||
| 	Records     []*models.Record | 	Records     []*models.Record | ||||||
| 	Result      *search.Result | 	Result      *search.Result | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordViewEvent struct { | type RecordViewEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordCreateEvent struct { | type RecordCreateEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordUpdateEvent struct { | type RecordUpdateEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordDeleteEvent struct { | type RecordDeleteEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
|  | @ -135,6 +183,8 @@ type RecordDeleteEvent struct { | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| type RecordAuthEvent struct { | type RecordAuthEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| 	Token       string | 	Token       string | ||||||
|  | @ -142,6 +192,8 @@ type RecordAuthEvent struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordAuthWithPasswordEvent struct { | type RecordAuthWithPasswordEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| 	Identity    string | 	Identity    string | ||||||
|  | @ -149,53 +201,73 @@ type RecordAuthWithPasswordEvent struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordAuthWithOAuth2Event struct { | type RecordAuthWithOAuth2Event struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| 	OAuth2User  *auth.AuthUser | 	OAuth2User  *auth.AuthUser | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordAuthRefreshEvent struct { | type RecordAuthRefreshEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordRequestPasswordResetEvent struct { | type RecordRequestPasswordResetEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordConfirmPasswordResetEvent struct { | type RecordConfirmPasswordResetEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordRequestVerificationEvent struct { | type RecordRequestVerificationEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordConfirmVerificationEvent struct { | type RecordConfirmVerificationEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordRequestEmailChangeEvent struct { | type RecordRequestEmailChangeEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordConfirmEmailChangeEvent struct { | type RecordConfirmEmailChangeEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordListExternalAuthsEvent struct { | type RecordListExternalAuthsEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext   echo.Context | 	HttpContext   echo.Context | ||||||
| 	Record        *models.Record | 	Record        *models.Record | ||||||
| 	ExternalAuths []*models.ExternalAuth | 	ExternalAuths []*models.ExternalAuth | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type RecordUnlinkExternalAuthEvent struct { | type RecordUnlinkExternalAuthEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext  echo.Context | 	HttpContext  echo.Context | ||||||
| 	Record       *models.Record | 	Record       *models.Record | ||||||
| 	ExternalAuth *models.ExternalAuth | 	ExternalAuth *models.ExternalAuth | ||||||
|  | @ -270,23 +342,27 @@ type CollectionsListEvent struct { | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CollectionViewEvent struct { | type CollectionViewEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Collection  *models.Collection |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CollectionCreateEvent struct { | type CollectionCreateEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Collection  *models.Collection |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CollectionUpdateEvent struct { | type CollectionUpdateEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Collection  *models.Collection |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CollectionDeleteEvent struct { | type CollectionDeleteEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Collection  *models.Collection |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type CollectionsImportEvent struct { | type CollectionsImportEvent struct { | ||||||
|  | @ -299,8 +375,9 @@ type CollectionsImportEvent struct { | ||||||
| // -------------------------------------------------------------------
 | // -------------------------------------------------------------------
 | ||||||
| 
 | 
 | ||||||
| type FileDownloadEvent struct { | type FileDownloadEvent struct { | ||||||
|  | 	BaseCollectionEvent | ||||||
|  | 
 | ||||||
| 	HttpContext echo.Context | 	HttpContext echo.Context | ||||||
| 	Collection  *models.Collection |  | ||||||
| 	Record      *models.Record | 	Record      *models.Record | ||||||
| 	FileField   *schema.SchemaField | 	FileField   *schema.SchemaField | ||||||
| 	ServedPath  string | 	ServedPath  string | ||||||
|  |  | ||||||
|  | @ -0,0 +1,84 @@ | ||||||
|  | package core_test | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	"github.com/pocketbase/pocketbase/core" | ||||||
|  | 	"github.com/pocketbase/pocketbase/models" | ||||||
|  | 	"github.com/pocketbase/pocketbase/tools/list" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestBaseCollectionEventTags(t *testing.T) { | ||||||
|  | 	c1 := new(models.Collection) | ||||||
|  | 
 | ||||||
|  | 	c2 := new(models.Collection) | ||||||
|  | 	c2.Id = "a" | ||||||
|  | 
 | ||||||
|  | 	c3 := new(models.Collection) | ||||||
|  | 	c3.Name = "b" | ||||||
|  | 
 | ||||||
|  | 	c4 := new(models.Collection) | ||||||
|  | 	c4.Id = "a" | ||||||
|  | 	c4.Name = "b" | ||||||
|  | 
 | ||||||
|  | 	scenarios := []struct { | ||||||
|  | 		collection   *models.Collection | ||||||
|  | 		expectedTags []string | ||||||
|  | 	}{ | ||||||
|  | 		{c1, []string{}}, | ||||||
|  | 		{c2, []string{"a"}}, | ||||||
|  | 		{c3, []string{"b"}}, | ||||||
|  | 		{c4, []string{"a", "b"}}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, s := range scenarios { | ||||||
|  | 		event := new(core.BaseCollectionEvent) | ||||||
|  | 		event.Collection = s.collection | ||||||
|  | 
 | ||||||
|  | 		tags := event.Tags() | ||||||
|  | 
 | ||||||
|  | 		if len(s.expectedTags) != len(tags) { | ||||||
|  | 			t.Fatalf("[%d] Expected %v tags, got %v", i, s.expectedTags, tags) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, tag := range s.expectedTags { | ||||||
|  | 			if !list.ExistInSlice(tag, tags) { | ||||||
|  | 				t.Fatalf("[%d] Expected %v tags, got %v", i, s.expectedTags, tags) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestModelEventTags(t *testing.T) { | ||||||
|  | 	m1 := new(models.Admin) | ||||||
|  | 
 | ||||||
|  | 	c := new(models.Collection) | ||||||
|  | 	c.Id = "a" | ||||||
|  | 	c.Name = "b" | ||||||
|  | 	m2 := models.NewRecord(c) | ||||||
|  | 
 | ||||||
|  | 	scenarios := []struct { | ||||||
|  | 		model        models.Model | ||||||
|  | 		expectedTags []string | ||||||
|  | 	}{ | ||||||
|  | 		{m1, []string{"_admins"}}, | ||||||
|  | 		{m2, []string{"a", "b"}}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, s := range scenarios { | ||||||
|  | 		event := new(core.ModelEvent) | ||||||
|  | 		event.Model = s.model | ||||||
|  | 
 | ||||||
|  | 		tags := event.Tags() | ||||||
|  | 
 | ||||||
|  | 		if len(s.expectedTags) != len(tags) { | ||||||
|  | 			t.Fatalf("[%d] Expected %v tags, got %v", i, s.expectedTags, tags) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		for _, tag := range s.expectedTags { | ||||||
|  | 			if !list.ExistInSlice(tag, tags) { | ||||||
|  | 				t.Fatalf("[%d] Expected %v tags, got %v", i, s.expectedTags, tags) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | @ -61,12 +61,11 @@ func SendAdminPasswordReset(app core.App, admin *models.Admin) error { | ||||||
| 		HTML:    body, | 		HTML:    body, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.MailerAdminEvent{ | 	event := new(core.MailerAdminEvent) | ||||||
| 		MailClient: mailClient, | 	event.MailClient = mailClient | ||||||
| 		Message:    message, | 	event.Message = message | ||||||
| 		Admin:      admin, | 	event.Admin = admin | ||||||
| 		Meta:       map[string]any{"token": token}, | 	event.Meta = map[string]any{"token": token} | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	sendErr := app.OnMailerBeforeAdminResetPasswordSend().Trigger(event, func(e *core.MailerAdminEvent) error { | 	sendErr := app.OnMailerBeforeAdminResetPasswordSend().Trigger(event, func(e *core.MailerAdminEvent) error { | ||||||
| 		return e.MailClient.Send(e.Message) | 		return e.MailClient.Send(e.Message) | ||||||
|  |  | ||||||
|  | @ -37,12 +37,12 @@ func SendRecordPasswordReset(app core.App, authRecord *models.Record) error { | ||||||
| 		HTML:    body, | 		HTML:    body, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.MailerRecordEvent{ | 	event := new(core.MailerRecordEvent) | ||||||
| 		MailClient: mailClient, | 	event.MailClient = mailClient | ||||||
| 		Message:    message, | 	event.Message = message | ||||||
| 		Record:     authRecord, | 	event.Collection = authRecord.Collection() | ||||||
| 		Meta:       map[string]any{"token": token}, | 	event.Record = authRecord | ||||||
| 	} | 	event.Meta = map[string]any{"token": token} | ||||||
| 
 | 
 | ||||||
| 	sendErr := app.OnMailerBeforeRecordResetPasswordSend().Trigger(event, func(e *core.MailerRecordEvent) error { | 	sendErr := app.OnMailerBeforeRecordResetPasswordSend().Trigger(event, func(e *core.MailerRecordEvent) error { | ||||||
| 		return e.MailClient.Send(e.Message) | 		return e.MailClient.Send(e.Message) | ||||||
|  | @ -81,12 +81,12 @@ func SendRecordVerification(app core.App, authRecord *models.Record) error { | ||||||
| 		HTML:    body, | 		HTML:    body, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.MailerRecordEvent{ | 	event := new(core.MailerRecordEvent) | ||||||
| 		MailClient: mailClient, | 	event.MailClient = mailClient | ||||||
| 		Message:    message, | 	event.Message = message | ||||||
| 		Record:     authRecord, | 	event.Collection = authRecord.Collection() | ||||||
| 		Meta:       map[string]any{"token": token}, | 	event.Record = authRecord | ||||||
| 	} | 	event.Meta = map[string]any{"token": token} | ||||||
| 
 | 
 | ||||||
| 	sendErr := app.OnMailerBeforeRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error { | 	sendErr := app.OnMailerBeforeRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error { | ||||||
| 		return e.MailClient.Send(e.Message) | 		return e.MailClient.Send(e.Message) | ||||||
|  | @ -125,14 +125,14 @@ func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string) | ||||||
| 		HTML:    body, | 		HTML:    body, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event := &core.MailerRecordEvent{ | 	event := new(core.MailerRecordEvent) | ||||||
| 		MailClient: mailClient, | 	event.MailClient = mailClient | ||||||
| 		Message:    message, | 	event.Message = message | ||||||
| 		Record:     record, | 	event.Collection = record.Collection() | ||||||
| 		Meta: map[string]any{ | 	event.Record = record | ||||||
| 			"token":    token, | 	event.Meta = map[string]any{ | ||||||
| 			"newEmail": newEmail, | 		"token":    token, | ||||||
| 		}, | 		"newEmail": newEmail, | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	sendErr := app.OnMailerBeforeRecordChangeEmailSend().Trigger(event, func(e *core.MailerRecordEvent) error { | 	sendErr := app.OnMailerBeforeRecordChangeEmailSend().Trigger(event, func(e *core.MailerRecordEvent) error { | ||||||
|  |  | ||||||
|  | @ -5,7 +5,7 @@ import ( | ||||||
| 	"testing" | 	"testing" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestAddAndPreAdd(t *testing.T) { | func TestHookAddAndPreAdd(t *testing.T) { | ||||||
| 	h := Hook[int]{} | 	h := Hook[int]{} | ||||||
| 
 | 
 | ||||||
| 	if total := len(h.handlers); total != 0 { | 	if total := len(h.handlers); total != 0 { | ||||||
|  | @ -36,7 +36,7 @@ func TestAddAndPreAdd(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestReset(t *testing.T) { | func TestHookReset(t *testing.T) { | ||||||
| 	h := Hook[int]{} | 	h := Hook[int]{} | ||||||
| 
 | 
 | ||||||
| 	h.Reset() // should do nothing and not panic
 | 	h.Reset() // should do nothing and not panic
 | ||||||
|  | @ -55,7 +55,7 @@ func TestReset(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestTrigger(t *testing.T) { | func TestHookTrigger(t *testing.T) { | ||||||
| 	err1 := errors.New("demo") | 	err1 := errors.New("demo") | ||||||
| 	err2 := errors.New("demo") | 	err2 := errors.New("demo") | ||||||
| 
 | 
 | ||||||
|  | @ -92,7 +92,7 @@ func TestTrigger(t *testing.T) { | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func TestTriggerStopPropagation(t *testing.T) { | func TestHookTriggerStopPropagation(t *testing.T) { | ||||||
| 	called1 := false | 	called1 := false | ||||||
| 	f1 := func(data int) error { called1 = true; return nil } | 	f1 := func(data int) error { called1 = true; return nil } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,74 @@ | ||||||
|  | package hook | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"github.com/pocketbase/pocketbase/tools/list" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | // Tagger defines an interface for event data structs that support tags/groups/categories/etc.
 | ||||||
|  | // Usually used together with TaggedHook.
 | ||||||
|  | type Tagger interface { | ||||||
|  | 	Tags() []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // wrapped local Hook embedded struct to limit the public API surface.
 | ||||||
|  | type mainHook[T Tagger] struct { | ||||||
|  | 	*Hook[T] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // NewTaggedHook creates a new TaggedHook with the provided main hook and optional tags.
 | ||||||
|  | func NewTaggedHook[T Tagger](hook *Hook[T], tags ...string) *TaggedHook[T] { | ||||||
|  | 	return &TaggedHook[T]{ | ||||||
|  | 		mainHook[T]{hook}, | ||||||
|  | 		tags, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TaggedHook defines a proxy hook which register handlers that are triggered only
 | ||||||
|  | // if the TaggedHook.tags are empty or includes at least one of the event data tag(s).
 | ||||||
|  | type TaggedHook[T Tagger] struct { | ||||||
|  | 	mainHook[T] | ||||||
|  | 
 | ||||||
|  | 	tags []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // CanTriggerOn checks if the current TaggedHook can be triggered with
 | ||||||
|  | // the provided event data tags.
 | ||||||
|  | func (p *TaggedHook[T]) CanTriggerOn(tags []string) bool { | ||||||
|  | 	if len(p.tags) == 0 { | ||||||
|  | 		return true // match all
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, t := range tags { | ||||||
|  | 		if list.ExistInSlice(t, p.tags) { | ||||||
|  | 			return true | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return false | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // PreAdd registers a new handler to the hook by prepending it to the existing queue.
 | ||||||
|  | //
 | ||||||
|  | // The fn handler will be called only if the event data tags satisfy p.CanTriggerOn.
 | ||||||
|  | func (p *TaggedHook[T]) PreAdd(fn Handler[T]) { | ||||||
|  | 	p.mainHook.PreAdd(func(e T) error { | ||||||
|  | 		if p.CanTriggerOn(e.Tags()) { | ||||||
|  | 			return fn(e) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Add registers a new handler to the hook by appending it to the existing queue.
 | ||||||
|  | //
 | ||||||
|  | // The fn handler will be called only if the event data tags satisfy p.CanTriggerOn.
 | ||||||
|  | func (p *TaggedHook[T]) Add(fn Handler[T]) { | ||||||
|  | 	p.mainHook.Add(func(e T) error { | ||||||
|  | 		if p.CanTriggerOn(e.Tags()) { | ||||||
|  | 			return fn(e) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		return nil | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,69 @@ | ||||||
|  | package hook | ||||||
|  | 
 | ||||||
|  | import "testing" | ||||||
|  | 
 | ||||||
|  | type mockTagsData struct { | ||||||
|  | 	tags []string | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m mockTagsData) Tags() []string { | ||||||
|  | 	return m.tags | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestTaggedHook(t *testing.T) { | ||||||
|  | 	triggerSequence := "" | ||||||
|  | 
 | ||||||
|  | 	base := &Hook[mockTagsData]{} | ||||||
|  | 	base.Add(func(data mockTagsData) error { triggerSequence += "f0"; return nil }) | ||||||
|  | 
 | ||||||
|  | 	hA := NewTaggedHook(base) | ||||||
|  | 	hA.Add(func(data mockTagsData) error { triggerSequence += "a1"; return nil }) | ||||||
|  | 	hA.PreAdd(func(data mockTagsData) error { triggerSequence += "a2"; return nil }) | ||||||
|  | 
 | ||||||
|  | 	hB := NewTaggedHook(base, "b1", "b2") | ||||||
|  | 	hB.Add(func(data mockTagsData) error { triggerSequence += "b1"; return nil }) | ||||||
|  | 	hB.PreAdd(func(data mockTagsData) error { triggerSequence += "b2"; return nil }) | ||||||
|  | 
 | ||||||
|  | 	hC := NewTaggedHook(base, "c1", "c2") | ||||||
|  | 	hC.Add(func(data mockTagsData) error { triggerSequence += "c1"; return nil }) | ||||||
|  | 	hC.PreAdd(func(data mockTagsData) error { triggerSequence += "c2"; return nil }) | ||||||
|  | 
 | ||||||
|  | 	scenarios := []struct { | ||||||
|  | 		data             mockTagsData | ||||||
|  | 		expectedSequence string | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			mockTagsData{}, | ||||||
|  | 			"a2f0a1", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			mockTagsData{[]string{"missing"}}, | ||||||
|  | 			"a2f0a1", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			mockTagsData{[]string{"b2"}}, | ||||||
|  | 			"b2a2f0a1b1", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			mockTagsData{[]string{"c1"}}, | ||||||
|  | 			"c2a2f0a1c1", | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			mockTagsData{[]string{"b1", "c2"}}, | ||||||
|  | 			"c2b2a2f0a1b1c1", | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for i, s := range scenarios { | ||||||
|  | 		triggerSequence = "" // reset
 | ||||||
|  | 
 | ||||||
|  | 		err := hA.Trigger(s.data) | ||||||
|  | 		if err != nil { | ||||||
|  | 			t.Fatalf("[%d] Unexpected trigger error: %v", i, err) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if triggerSequence != s.expectedSequence { | ||||||
|  | 			t.Fatalf("[%d] Expected trigger sequence %s, got %s", i, s.expectedSequence, triggerSequence) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue