From b8d7609e9ebab02851da08a6ef419041f0d56d44 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Fri, 27 Jan 2023 22:19:08 +0200 Subject: [PATCH] added support for optional Model and Record event hook tags --- CHANGELOG.md | 2 + apis/admin.go | 72 +++++------- apis/base.go | 7 +- apis/collection.go | 44 ++++---- apis/file.go | 15 ++- apis/record_auth.go | 87 +++++++------- apis/record_crud.go | 43 ++++--- apis/record_helpers.go | 24 ++-- apis/settings.go | 14 +-- core/app.go | 218 ++++++++++++++++++++++++++++------- core/base.go | 172 ++++++++++++++-------------- core/base_test.go | 232 -------------------------------------- core/db.go | 2 +- core/events.go | 89 ++++++++++++++- core/events_test.go | 84 ++++++++++++++ mails/admin.go | 11 +- mails/record.go | 40 +++---- tools/hook/hook_test.go | 8 +- tools/hook/tagged.go | 74 ++++++++++++ tools/hook/tagged_test.go | 69 ++++++++++++ 20 files changed, 748 insertions(+), 559 deletions(-) create mode 100644 core/events_test.go create mode 100644 tools/hook/tagged.go create mode 100644 tools/hook/tagged_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ccd7b0fc..b347f0506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -56,6 +56,8 @@ store.GetAll() map[string]T ``` +- Added tagged/proxy hook for all Record and Model events (@todo document). + ## v0.11.4 diff --git a/apis/admin.go b/apis/admin.go index d63017962..45f08dfb0 100644 --- a/apis/admin.go +++ b/apis/admin.go @@ -39,11 +39,10 @@ func (api *adminApi) authResponse(c echo.Context, admin *models.Admin) error { return NewBadRequestError("Failed to create auth token.", tokenErr) } - event := &core.AdminAuthEvent{ - HttpContext: c, - Admin: admin, - Token: token, - } + event := new(core.AdminAuthEvent) + event.HttpContext = c + event.Admin = admin + event.Token = token return api.app.OnAdminAuthRequest().Trigger(event, func(e *core.AdminAuthEvent) error { 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) } - event := &core.AdminAuthRefreshEvent{ - HttpContext: c, - Admin: admin, - } + event := new(core.AdminAuthRefreshEvent) + event.HttpContext = c + event.Admin = admin handlerErr := api.app.OnAdminBeforeAuthRefreshRequest().Trigger(event, func(e *core.AdminAuthRefreshEvent) error { 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) } - event := &core.AdminAuthWithPasswordEvent{ - HttpContext: c, - Password: form.Password, - Identity: form.Identity, - } + event := new(core.AdminAuthWithPasswordEvent) + event.HttpContext = c + event.Password = form.Password + event.Identity = form.Identity _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { 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) } - event := &core.AdminRequestPasswordResetEvent{ - HttpContext: c, - } + event := new(core.AdminRequestPasswordResetEvent) + event.HttpContext = c submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { 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) } - event := &core.AdminConfirmPasswordResetEvent{ - HttpContext: c, - } + event := new(core.AdminConfirmPasswordResetEvent) + event.HttpContext = c _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Admin]) forms.InterceptorNextFunc[*models.Admin] { return func(admin *models.Admin) error { @@ -207,11 +202,10 @@ func (api *adminApi) list(c echo.Context) error { return NewBadRequestError("", err) } - event := &core.AdminsListEvent{ - HttpContext: c, - Admins: admins, - Result: result, - } + event := new(core.AdminsListEvent) + event.HttpContext = c + event.Admins = admins + event.Result = result return api.app.OnAdminsListRequest().Trigger(event, func(e *core.AdminsListEvent) error { return e.HttpContext.JSON(http.StatusOK, e.Result) @@ -229,10 +223,9 @@ func (api *adminApi) view(c echo.Context) error { return NewNotFoundError("", err) } - event := &core.AdminViewEvent{ - HttpContext: c, - Admin: admin, - } + event := new(core.AdminViewEvent) + event.HttpContext = c + event.Admin = admin return api.app.OnAdminViewRequest().Trigger(event, func(e *core.AdminViewEvent) error { 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) } - event := &core.AdminCreateEvent{ - HttpContext: c, - Admin: admin, - } + event := new(core.AdminCreateEvent) + event.HttpContext = c + event.Admin = admin // create the 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) } - event := &core.AdminUpdateEvent{ - HttpContext: c, - Admin: admin, - } + event := new(core.AdminUpdateEvent) + event.HttpContext = c + event.Admin = admin // update the 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) } - event := &core.AdminDeleteEvent{ - HttpContext: c, - Admin: admin, - } + event := new(core.AdminDeleteEvent) + event.HttpContext = c + event.Admin = admin handlerErr := api.app.OnAdminBeforeDeleteRequest().Trigger(event, func(e *core.AdminDeleteEvent) error { if err := api.app.Dao().DeleteAdmin(e.Admin); err != nil { diff --git a/apis/base.go b/apis/base.go index 4b3532d84..0698554a9 100644 --- a/apis/base.go +++ b/apis/base.go @@ -71,10 +71,9 @@ func InitApi(app core.App) (*echo.Echo, error) { apiErr = NewBadRequestError("", err) } - event := &core.ApiErrorEvent{ - HttpContext: c, - Error: apiErr, - } + event := new(core.ApiErrorEvent) + event.HttpContext = c + event.Error = apiErr // send error response hookErr := app.OnBeforeApiError().Trigger(event, func(e *core.ApiErrorEvent) error { diff --git a/apis/collection.go b/apis/collection.go index 8bb0bf4a5..313bd1702 100644 --- a/apis/collection.go +++ b/apis/collection.go @@ -43,11 +43,10 @@ func (api *collectionApi) list(c echo.Context) error { return NewBadRequestError("", err) } - event := &core.CollectionsListEvent{ - HttpContext: c, - Collections: collections, - Result: result, - } + event := new(core.CollectionsListEvent) + event.HttpContext = c + event.Collections = collections + event.Result = result return api.app.OnCollectionsListRequest().Trigger(event, func(e *core.CollectionsListEvent) error { return e.HttpContext.JSON(http.StatusOK, e.Result) @@ -60,10 +59,9 @@ func (api *collectionApi) view(c echo.Context) error { return NewNotFoundError("", err) } - event := &core.CollectionViewEvent{ - HttpContext: c, - Collection: collection, - } + event := new(core.CollectionViewEvent) + event.HttpContext = c + event.Collection = collection return api.app.OnCollectionViewRequest().Trigger(event, func(e *core.CollectionViewEvent) error { 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) } - event := &core.CollectionCreateEvent{ - HttpContext: c, - Collection: collection, - } + event := new(core.CollectionCreateEvent) + event.HttpContext = c + event.Collection = collection // create the 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) } - event := &core.CollectionUpdateEvent{ - HttpContext: c, - Collection: collection, - } + event := new(core.CollectionUpdateEvent) + event.HttpContext = c + event.Collection = collection // update the 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) } - event := &core.CollectionDeleteEvent{ - HttpContext: c, - Collection: collection, - } + event := new(core.CollectionDeleteEvent) + event.HttpContext = c + event.Collection = collection handlerErr := api.app.OnCollectionBeforeDeleteRequest().Trigger(event, func(e *core.CollectionDeleteEvent) error { 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) } - event := &core.CollectionsImportEvent{ - HttpContext: c, - Collections: form.Collections, - } + event := new(core.CollectionsImportEvent) + event.HttpContext = c + event.Collections = form.Collections // import collections submitErr := form.Submit(func(next forms.InterceptorNextFunc[[]*models.Collection]) forms.InterceptorNextFunc[[]*models.Collection] { diff --git a/apis/file.go b/apis/file.go index dd1810742..25756f6df 100644 --- a/apis/file.go +++ b/apis/file.go @@ -84,14 +84,13 @@ func (api *fileApi) download(c echo.Context) error { } } - event := &core.FileDownloadEvent{ - HttpContext: c, - Record: record, - Collection: collection, - FileField: fileField, - ServedPath: servedPath, - ServedName: servedName, - } + event := new(core.FileDownloadEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record + event.FileField = fileField + event.ServedPath = servedPath + event.ServedName = servedName return api.app.OnFileDownloadRequest().Trigger(event, func(e *core.FileDownloadEvent) error { res := e.HttpContext.Response() diff --git a/apis/record_auth.go b/apis/record_auth.go index 42669dd77..80c90b5be 100644 --- a/apis/record_auth.go +++ b/apis/record_auth.go @@ -55,10 +55,10 @@ func (api *recordAuthApi) authRefresh(c echo.Context) error { return NewNotFoundError("Missing auth record context.", nil) } - event := &core.RecordAuthRefreshEvent{ - HttpContext: c, - Record: record, - } + event := new(core.RecordAuthRefreshEvent) + event.HttpContext = c + event.Collection = record.Collection() + event.Record = record handlerErr := api.app.OnRecordBeforeAuthRefreshRequest().Trigger(event, func(e *core.RecordAuthRefreshEvent) error { return RecordAuthResponse(api.app, e.HttpContext, e.Record, nil) @@ -204,9 +204,9 @@ func (api *recordAuthApi) authWithOAuth2(c echo.Context) error { }) }) - event := &core.RecordAuthWithOAuth2Event{ - HttpContext: c, - } + event := new(core.RecordAuthWithOAuth2Event) + event.HttpContext = c + event.Collection = collection _, _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*forms.RecordOAuth2LoginData]) forms.InterceptorNextFunc[*forms.RecordOAuth2LoginData] { 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) } - event := &core.RecordAuthWithPasswordEvent{ - HttpContext: c, - Password: form.Password, - Identity: form.Identity, - } + event := new(core.RecordAuthWithPasswordEvent) + event.HttpContext = c + event.Collection = collection + event.Password = form.Password + event.Identity = form.Identity _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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) } - event := &core.RecordRequestPasswordResetEvent{ - HttpContext: c, - } + event := new(core.RecordRequestPasswordResetEvent) + event.HttpContext = c + event.Collection = collection submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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) } - event := &core.RecordConfirmPasswordResetEvent{ - HttpContext: c, - } + event := new(core.RecordConfirmPasswordResetEvent) + event.HttpContext = c + event.Collection = collection _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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) } - event := &core.RecordRequestVerificationEvent{ - HttpContext: c, - } + event := new(core.RecordRequestVerificationEvent) + event.HttpContext = c + event.Collection = collection submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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) } - event := &core.RecordConfirmVerificationEvent{ - HttpContext: c, - } + event := new(core.RecordConfirmVerificationEvent) + event.HttpContext = c + event.Collection = collection _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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 { + collection, _ := c.Get(ContextCollectionKey).(*models.Collection) + if collection == nil { + return NewNotFoundError("Missing collection context.", nil) + } + record, _ := c.Get(ContextAuthRecordKey).(*models.Record) if 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) } - event := &core.RecordRequestEmailChangeEvent{ - HttpContext: c, - Record: record, - } + event := new(core.RecordRequestEmailChangeEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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) } - event := &core.RecordConfirmEmailChangeEvent{ - HttpContext: c, - } + event := new(core.RecordConfirmEmailChangeEvent) + event.HttpContext = c + event.Collection = collection _, submitErr := form.Submit(func(next forms.InterceptorNextFunc[*models.Record]) forms.InterceptorNextFunc[*models.Record] { 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) } - event := &core.RecordListExternalAuthsEvent{ - HttpContext: c, - Record: record, - ExternalAuths: externalAuths, - } + event := new(core.RecordListExternalAuthsEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record + event.ExternalAuths = externalAuths return api.app.OnRecordListExternalAuthsRequest().Trigger(event, func(e *core.RecordListExternalAuthsEvent) error { 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) } - event := &core.RecordUnlinkExternalAuthEvent{ - HttpContext: c, - Record: record, - ExternalAuth: externalAuth, - } + event := new(core.RecordUnlinkExternalAuthEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record + event.ExternalAuth = externalAuth handlerErr := api.app.OnRecordBeforeUnlinkExternalAuthRequest().Trigger(event, func(e *core.RecordUnlinkExternalAuthEvent) error { if err := api.app.Dao().DeleteExternalAuth(externalAuth); err != nil { diff --git a/apis/record_crud.go b/apis/record_crud.go index 4e5c75bc3..6ad4b6ab4 100644 --- a/apis/record_crud.go +++ b/apis/record_crud.go @@ -83,12 +83,11 @@ func (api *recordApi) list(c echo.Context) error { result.Items = records - event := &core.RecordsListEvent{ - HttpContext: c, - Collection: collection, - Records: records, - Result: result, - } + event := new(core.RecordsListEvent) + event.HttpContext = c + event.Collection = collection + event.Records = records + event.Result = result 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() { @@ -135,10 +134,10 @@ func (api *recordApi) view(c echo.Context) error { return NewNotFoundError("", fetchErr) } - event := &core.RecordViewEvent{ - HttpContext: c, - Record: record, - } + event := new(core.RecordViewEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record 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() { @@ -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) } - event := &core.RecordCreateEvent{ - HttpContext: c, - Record: record, - } + event := new(core.RecordCreateEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record // create the 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) } - event := &core.RecordUpdateEvent{ - HttpContext: c, - Record: record, - } + event := new(core.RecordUpdateEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record // update the 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) } - event := &core.RecordDeleteEvent{ - HttpContext: c, - Record: record, - } + event := new(core.RecordDeleteEvent) + event.HttpContext = c + event.Collection = collection + event.Record = record handlerErr := api.app.OnRecordBeforeDeleteRequest().Trigger(event, func(e *core.RecordDeleteEvent) error { // delete the record diff --git a/apis/record_helpers.go b/apis/record_helpers.go index a8f77954d..96e7de31c 100644 --- a/apis/record_helpers.go +++ b/apis/record_helpers.go @@ -51,12 +51,12 @@ func RecordAuthResponse(app core.App, c echo.Context, authRecord *models.Record, return NewBadRequestError("Failed to create auth token.", tokenErr) } - event := &core.RecordAuthEvent{ - HttpContext: c, - Record: authRecord, - Token: token, - Meta: meta, - } + event := new(core.RecordAuthEvent) + event.HttpContext = c + event.Collection = authRecord.Collection() + event.Record = authRecord + event.Token = token + event.Meta = meta return app.OnRecordAuthRequest().Trigger(event, func(e *core.RecordAuthEvent) error { // 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: -// - expands relations (if defaultExpands and/or ?expand query param is set) -// - 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 +// - expands relations (if defaultExpands and/or ?expand query param is set) +// - 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 func EnrichRecord(c echo.Context, dao *daos.Dao, record *models.Record, defaultExpands ...string) error { return EnrichRecords(c, dao, []*models.Record{record}, defaultExpands...) } // EnrichRecords parses the request context and enriches the provided records: -// - expands relations (if defaultExpands and/or ?expand query param is set) -// - 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 +// - expands relations (if defaultExpands and/or ?expand query param is set) +// - 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 func EnrichRecords(c echo.Context, dao *daos.Dao, records []*models.Record, defaultExpands ...string) error { requestData := RequestData(c) diff --git a/apis/settings.go b/apis/settings.go index 04477b787..6624d0ff4 100644 --- a/apis/settings.go +++ b/apis/settings.go @@ -34,10 +34,9 @@ func (api *settingsApi) list(c echo.Context) error { return NewBadRequestError("", err) } - event := &core.SettingsListEvent{ - HttpContext: c, - RedactedSettings: settings, - } + event := new(core.SettingsListEvent) + event.HttpContext = c + event.RedactedSettings = settings return api.app.OnSettingsListRequest().Trigger(event, func(e *core.SettingsListEvent) error { 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) } - event := &core.SettingsUpdateEvent{ - HttpContext: c, - OldSettings: api.app.Settings(), - } + event := new(core.SettingsUpdateEvent) + event.HttpContext = c + event.OldSettings = api.app.Settings() // update the settings submitErr := form.Submit(func(next forms.InterceptorNextFunc[*settings.Settings]) forms.InterceptorNextFunc[*settings.Settings] { diff --git a/core/app.go b/core/app.go index 6c99f09dc..e5176457b 100644 --- a/core/app.go +++ b/core/app.go @@ -124,27 +124,51 @@ type App interface { // OnModelBeforeCreate hook is triggered before inserting a new // 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 // 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 // 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 // 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 // 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 // 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 @@ -166,33 +190,51 @@ type App interface { // // Could be used to send your own custom email template if // [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 // 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 // sending a verification email to an auth record. // // Could be used to send your own custom email template if // [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 // 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 // sending a confirmation new address email to an auth record. // // Could be used to send your own custom email template if // [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 // 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 @@ -257,7 +299,7 @@ type App interface { // // Could be used to validate or modify the file response before // returning it to the client. - OnFileDownloadRequest() *hook.Hook[*FileDownloadEvent] + OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*FileDownloadEvent] // --------------------------------------------------------------- // Admin API event hooks @@ -366,18 +408,27 @@ type App interface { // // Could be used to additionally validate or modify the authenticated // 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 // auth with password API request (after request data load and before password validation). // // Could be used to implement for example a custom password validation // 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 // 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 // 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 // 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 // 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 // auth refresh API request (right before generating a new auth token). // // Could be used to additionally validate the request data or implement // 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 // 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 // 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 // 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 // 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 // confirm password reset API request (after request data load and before persistence). // // Could be used to additionally validate the request data or implement // 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 // 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 // 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 // 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 // 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 // confirm verification API request (after request data load and before persistence). // // Could be used to additionally validate the request data or implement // 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 // 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 // (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 // 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 // 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 // confirm email change API request (after request data load and before persistence). // // Could be used to additionally validate the request data or implement // 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 // 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. // // 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 // external auth unlink request (after models load and before the actual relation deletion). // // Could be used to additionally validate the request data or implement // 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 // 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 @@ -493,45 +601,69 @@ type App interface { // 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. - 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. // // 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 // create request (after request data load and before model persistence). // // Could be used to additionally validate the request data or implement // 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 // 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 // update request (after request data load and before model persistence). // // Could be used to additionally validate the request data or implement // 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 // 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 // delete request (after model load and before actual deletion). // // Could be used to additionally validate the request data or implement // 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 // 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 diff --git a/core/base.go b/core/base.go index 2dd93a4c9..201659139 100644 --- a/core/base.go +++ b/core/base.go @@ -541,28 +541,28 @@ func (app *BaseApp) OnAfterApiError() *hook.Hook[*ApiErrorEvent] { // Dao event hooks // ------------------------------------------------------------------- -func (app *BaseApp) OnModelBeforeCreate() *hook.Hook[*ModelEvent] { - return app.onModelBeforeCreate +func (app *BaseApp) OnModelBeforeCreate(tags ...string) *hook.TaggedHook[*ModelEvent] { + return hook.NewTaggedHook(app.onModelBeforeCreate, tags...) } -func (app *BaseApp) OnModelAfterCreate() *hook.Hook[*ModelEvent] { - return app.onModelAfterCreate +func (app *BaseApp) OnModelAfterCreate(tags ...string) *hook.TaggedHook[*ModelEvent] { + return hook.NewTaggedHook(app.onModelAfterCreate, tags...) } -func (app *BaseApp) OnModelBeforeUpdate() *hook.Hook[*ModelEvent] { - return app.onModelBeforeUpdate +func (app *BaseApp) OnModelBeforeUpdate(tags ...string) *hook.TaggedHook[*ModelEvent] { + return hook.NewTaggedHook(app.onModelBeforeUpdate, tags...) } -func (app *BaseApp) OnModelAfterUpdate() *hook.Hook[*ModelEvent] { - return app.onModelAfterUpdate +func (app *BaseApp) OnModelAfterUpdate(tags ...string) *hook.TaggedHook[*ModelEvent] { + return hook.NewTaggedHook(app.onModelAfterUpdate, tags...) } -func (app *BaseApp) OnModelBeforeDelete() *hook.Hook[*ModelEvent] { - return app.onModelBeforeDelete +func (app *BaseApp) OnModelBeforeDelete(tags ...string) *hook.TaggedHook[*ModelEvent] { + return hook.NewTaggedHook(app.onModelBeforeDelete, tags...) } -func (app *BaseApp) OnModelAfterDelete() *hook.Hook[*ModelEvent] { - return app.onModelAfterDelete +func (app *BaseApp) OnModelAfterDelete(tags ...string) *hook.TaggedHook[*ModelEvent] { + return hook.NewTaggedHook(app.onModelAfterDelete, tags...) } // ------------------------------------------------------------------- @@ -577,28 +577,28 @@ func (app *BaseApp) OnMailerAfterAdminResetPasswordSend() *hook.Hook[*MailerAdmi return app.onMailerAfterAdminResetPasswordSend } -func (app *BaseApp) OnMailerBeforeRecordResetPasswordSend() *hook.Hook[*MailerRecordEvent] { - return app.onMailerBeforeRecordResetPasswordSend +func (app *BaseApp) OnMailerBeforeRecordResetPasswordSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { + return hook.NewTaggedHook(app.onMailerBeforeRecordResetPasswordSend, tags...) } -func (app *BaseApp) OnMailerAfterRecordResetPasswordSend() *hook.Hook[*MailerRecordEvent] { - return app.onMailerAfterRecordResetPasswordSend +func (app *BaseApp) OnMailerAfterRecordResetPasswordSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { + return hook.NewTaggedHook(app.onMailerAfterRecordResetPasswordSend, tags...) } -func (app *BaseApp) OnMailerBeforeRecordVerificationSend() *hook.Hook[*MailerRecordEvent] { - return app.onMailerBeforeRecordVerificationSend +func (app *BaseApp) OnMailerBeforeRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { + return hook.NewTaggedHook(app.onMailerBeforeRecordVerificationSend, tags...) } -func (app *BaseApp) OnMailerAfterRecordVerificationSend() *hook.Hook[*MailerRecordEvent] { - return app.onMailerAfterRecordVerificationSend +func (app *BaseApp) OnMailerAfterRecordVerificationSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { + return hook.NewTaggedHook(app.onMailerAfterRecordVerificationSend, tags...) } -func (app *BaseApp) OnMailerBeforeRecordChangeEmailSend() *hook.Hook[*MailerRecordEvent] { - return app.onMailerBeforeRecordChangeEmailSend +func (app *BaseApp) OnMailerBeforeRecordChangeEmailSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { + return hook.NewTaggedHook(app.onMailerBeforeRecordChangeEmailSend, tags...) } -func (app *BaseApp) OnMailerAfterRecordChangeEmailSend() *hook.Hook[*MailerRecordEvent] { - return app.onMailerAfterRecordChangeEmailSend +func (app *BaseApp) OnMailerAfterRecordChangeEmailSend(tags ...string) *hook.TaggedHook[*MailerRecordEvent] { + return hook.NewTaggedHook(app.onMailerAfterRecordChangeEmailSend, tags...) } // ------------------------------------------------------------------- @@ -649,8 +649,8 @@ func (app *BaseApp) OnSettingsAfterUpdateRequest() *hook.Hook[*SettingsUpdateEve // File API event hooks // ------------------------------------------------------------------- -func (app *BaseApp) OnFileDownloadRequest() *hook.Hook[*FileDownloadEvent] { - return app.onFileDownloadRequest +func (app *BaseApp) OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*FileDownloadEvent] { + return hook.NewTaggedHook(app.onFileDownloadRequest, tags...) } // ------------------------------------------------------------------- @@ -729,128 +729,128 @@ func (app *BaseApp) OnAdminAfterConfirmPasswordResetRequest() *hook.Hook[*AdminC // Record auth API event hooks // ------------------------------------------------------------------- -func (app *BaseApp) OnRecordAuthRequest() *hook.Hook[*RecordAuthEvent] { - return app.onRecordAuthRequest +func (app *BaseApp) OnRecordAuthRequest(tags ...string) *hook.TaggedHook[*RecordAuthEvent] { + return hook.NewTaggedHook(app.onRecordAuthRequest, tags...) } -func (app *BaseApp) OnRecordBeforeAuthWithPasswordRequest() *hook.Hook[*RecordAuthWithPasswordEvent] { - return app.onRecordBeforeAuthWithPasswordRequest +func (app *BaseApp) OnRecordBeforeAuthWithPasswordRequest(tags ...string) *hook.TaggedHook[*RecordAuthWithPasswordEvent] { + return hook.NewTaggedHook(app.onRecordBeforeAuthWithPasswordRequest, tags...) } -func (app *BaseApp) OnRecordAfterAuthWithPasswordRequest() *hook.Hook[*RecordAuthWithPasswordEvent] { - return app.onRecordAfterAuthWithPasswordRequest +func (app *BaseApp) OnRecordAfterAuthWithPasswordRequest(tags ...string) *hook.TaggedHook[*RecordAuthWithPasswordEvent] { + return hook.NewTaggedHook(app.onRecordAfterAuthWithPasswordRequest, tags...) } -func (app *BaseApp) OnRecordBeforeAuthWithOAuth2Request() *hook.Hook[*RecordAuthWithOAuth2Event] { - return app.onRecordBeforeAuthWithOAuth2Request +func (app *BaseApp) OnRecordBeforeAuthWithOAuth2Request(tags ...string) *hook.TaggedHook[*RecordAuthWithOAuth2Event] { + return hook.NewTaggedHook(app.onRecordBeforeAuthWithOAuth2Request, tags...) } -func (app *BaseApp) OnRecordAfterAuthWithOAuth2Request() *hook.Hook[*RecordAuthWithOAuth2Event] { - return app.onRecordAfterAuthWithOAuth2Request +func (app *BaseApp) OnRecordAfterAuthWithOAuth2Request(tags ...string) *hook.TaggedHook[*RecordAuthWithOAuth2Event] { + return hook.NewTaggedHook(app.onRecordAfterAuthWithOAuth2Request, tags...) } -func (app *BaseApp) OnRecordBeforeAuthRefreshRequest() *hook.Hook[*RecordAuthRefreshEvent] { - return app.onRecordBeforeAuthRefreshRequest +func (app *BaseApp) OnRecordBeforeAuthRefreshRequest(tags ...string) *hook.TaggedHook[*RecordAuthRefreshEvent] { + return hook.NewTaggedHook(app.onRecordBeforeAuthRefreshRequest, tags...) } -func (app *BaseApp) OnRecordAfterAuthRefreshRequest() *hook.Hook[*RecordAuthRefreshEvent] { - return app.onRecordAfterAuthRefreshRequest +func (app *BaseApp) OnRecordAfterAuthRefreshRequest(tags ...string) *hook.TaggedHook[*RecordAuthRefreshEvent] { + return hook.NewTaggedHook(app.onRecordAfterAuthRefreshRequest, tags...) } -func (app *BaseApp) OnRecordBeforeRequestPasswordResetRequest() *hook.Hook[*RecordRequestPasswordResetEvent] { - return app.onRecordBeforeRequestPasswordResetRequest +func (app *BaseApp) OnRecordBeforeRequestPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordRequestPasswordResetEvent] { + return hook.NewTaggedHook(app.onRecordBeforeRequestPasswordResetRequest, tags...) } -func (app *BaseApp) OnRecordAfterRequestPasswordResetRequest() *hook.Hook[*RecordRequestPasswordResetEvent] { - return app.onRecordAfterRequestPasswordResetRequest +func (app *BaseApp) OnRecordAfterRequestPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordRequestPasswordResetEvent] { + return hook.NewTaggedHook(app.onRecordAfterRequestPasswordResetRequest, tags...) } -func (app *BaseApp) OnRecordBeforeConfirmPasswordResetRequest() *hook.Hook[*RecordConfirmPasswordResetEvent] { - return app.onRecordBeforeConfirmPasswordResetRequest +func (app *BaseApp) OnRecordBeforeConfirmPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordConfirmPasswordResetEvent] { + return hook.NewTaggedHook(app.onRecordBeforeConfirmPasswordResetRequest, tags...) } -func (app *BaseApp) OnRecordAfterConfirmPasswordResetRequest() *hook.Hook[*RecordConfirmPasswordResetEvent] { - return app.onRecordAfterConfirmPasswordResetRequest +func (app *BaseApp) OnRecordAfterConfirmPasswordResetRequest(tags ...string) *hook.TaggedHook[*RecordConfirmPasswordResetEvent] { + return hook.NewTaggedHook(app.onRecordAfterConfirmPasswordResetRequest, tags...) } -func (app *BaseApp) OnRecordBeforeRequestVerificationRequest() *hook.Hook[*RecordRequestVerificationEvent] { - return app.onRecordBeforeRequestVerificationRequest +func (app *BaseApp) OnRecordBeforeRequestVerificationRequest(tags ...string) *hook.TaggedHook[*RecordRequestVerificationEvent] { + return hook.NewTaggedHook(app.onRecordBeforeRequestVerificationRequest, tags...) } -func (app *BaseApp) OnRecordAfterRequestVerificationRequest() *hook.Hook[*RecordRequestVerificationEvent] { - return app.onRecordAfterRequestVerificationRequest +func (app *BaseApp) OnRecordAfterRequestVerificationRequest(tags ...string) *hook.TaggedHook[*RecordRequestVerificationEvent] { + return hook.NewTaggedHook(app.onRecordAfterRequestVerificationRequest, tags...) } -func (app *BaseApp) OnRecordBeforeConfirmVerificationRequest() *hook.Hook[*RecordConfirmVerificationEvent] { - return app.onRecordBeforeConfirmVerificationRequest +func (app *BaseApp) OnRecordBeforeConfirmVerificationRequest(tags ...string) *hook.TaggedHook[*RecordConfirmVerificationEvent] { + return hook.NewTaggedHook(app.onRecordBeforeConfirmVerificationRequest, tags...) } -func (app *BaseApp) OnRecordAfterConfirmVerificationRequest() *hook.Hook[*RecordConfirmVerificationEvent] { - return app.onRecordAfterConfirmVerificationRequest +func (app *BaseApp) OnRecordAfterConfirmVerificationRequest(tags ...string) *hook.TaggedHook[*RecordConfirmVerificationEvent] { + return hook.NewTaggedHook(app.onRecordAfterConfirmVerificationRequest, tags...) } -func (app *BaseApp) OnRecordBeforeRequestEmailChangeRequest() *hook.Hook[*RecordRequestEmailChangeEvent] { - return app.onRecordBeforeRequestEmailChangeRequest +func (app *BaseApp) OnRecordBeforeRequestEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordRequestEmailChangeEvent] { + return hook.NewTaggedHook(app.onRecordBeforeRequestEmailChangeRequest, tags...) } -func (app *BaseApp) OnRecordAfterRequestEmailChangeRequest() *hook.Hook[*RecordRequestEmailChangeEvent] { - return app.onRecordAfterRequestEmailChangeRequest +func (app *BaseApp) OnRecordAfterRequestEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordRequestEmailChangeEvent] { + return hook.NewTaggedHook(app.onRecordAfterRequestEmailChangeRequest, tags...) } -func (app *BaseApp) OnRecordBeforeConfirmEmailChangeRequest() *hook.Hook[*RecordConfirmEmailChangeEvent] { - return app.onRecordBeforeConfirmEmailChangeRequest +func (app *BaseApp) OnRecordBeforeConfirmEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordConfirmEmailChangeEvent] { + return hook.NewTaggedHook(app.onRecordBeforeConfirmEmailChangeRequest, tags...) } -func (app *BaseApp) OnRecordAfterConfirmEmailChangeRequest() *hook.Hook[*RecordConfirmEmailChangeEvent] { - return app.onRecordAfterConfirmEmailChangeRequest +func (app *BaseApp) OnRecordAfterConfirmEmailChangeRequest(tags ...string) *hook.TaggedHook[*RecordConfirmEmailChangeEvent] { + return hook.NewTaggedHook(app.onRecordAfterConfirmEmailChangeRequest, tags...) } -func (app *BaseApp) OnRecordListExternalAuthsRequest() *hook.Hook[*RecordListExternalAuthsEvent] { - return app.onRecordListExternalAuthsRequest +func (app *BaseApp) OnRecordListExternalAuthsRequest(tags ...string) *hook.TaggedHook[*RecordListExternalAuthsEvent] { + return hook.NewTaggedHook(app.onRecordListExternalAuthsRequest, tags...) } -func (app *BaseApp) OnRecordBeforeUnlinkExternalAuthRequest() *hook.Hook[*RecordUnlinkExternalAuthEvent] { - return app.onRecordBeforeUnlinkExternalAuthRequest +func (app *BaseApp) OnRecordBeforeUnlinkExternalAuthRequest(tags ...string) *hook.TaggedHook[*RecordUnlinkExternalAuthEvent] { + return hook.NewTaggedHook(app.onRecordBeforeUnlinkExternalAuthRequest, tags...) } -func (app *BaseApp) OnRecordAfterUnlinkExternalAuthRequest() *hook.Hook[*RecordUnlinkExternalAuthEvent] { - return app.onRecordAfterUnlinkExternalAuthRequest +func (app *BaseApp) OnRecordAfterUnlinkExternalAuthRequest(tags ...string) *hook.TaggedHook[*RecordUnlinkExternalAuthEvent] { + return hook.NewTaggedHook(app.onRecordAfterUnlinkExternalAuthRequest, tags...) } // ------------------------------------------------------------------- // Record CRUD API event hooks // ------------------------------------------------------------------- -func (app *BaseApp) OnRecordsListRequest() *hook.Hook[*RecordsListEvent] { - return app.onRecordsListRequest +func (app *BaseApp) OnRecordsListRequest(tags ...string) *hook.TaggedHook[*RecordsListEvent] { + return hook.NewTaggedHook(app.onRecordsListRequest, tags...) } -func (app *BaseApp) OnRecordViewRequest() *hook.Hook[*RecordViewEvent] { - return app.onRecordViewRequest +func (app *BaseApp) OnRecordViewRequest(tags ...string) *hook.TaggedHook[*RecordViewEvent] { + return hook.NewTaggedHook(app.onRecordViewRequest, tags...) } -func (app *BaseApp) OnRecordBeforeCreateRequest() *hook.Hook[*RecordCreateEvent] { - return app.onRecordBeforeCreateRequest +func (app *BaseApp) OnRecordBeforeCreateRequest(tags ...string) *hook.TaggedHook[*RecordCreateEvent] { + return hook.NewTaggedHook(app.onRecordBeforeCreateRequest, tags...) } -func (app *BaseApp) OnRecordAfterCreateRequest() *hook.Hook[*RecordCreateEvent] { - return app.onRecordAfterCreateRequest +func (app *BaseApp) OnRecordAfterCreateRequest(tags ...string) *hook.TaggedHook[*RecordCreateEvent] { + return hook.NewTaggedHook(app.onRecordAfterCreateRequest, tags...) } -func (app *BaseApp) OnRecordBeforeUpdateRequest() *hook.Hook[*RecordUpdateEvent] { - return app.onRecordBeforeUpdateRequest +func (app *BaseApp) OnRecordBeforeUpdateRequest(tags ...string) *hook.TaggedHook[*RecordUpdateEvent] { + return hook.NewTaggedHook(app.onRecordBeforeUpdateRequest, tags...) } -func (app *BaseApp) OnRecordAfterUpdateRequest() *hook.Hook[*RecordUpdateEvent] { - return app.onRecordAfterUpdateRequest +func (app *BaseApp) OnRecordAfterUpdateRequest(tags ...string) *hook.TaggedHook[*RecordUpdateEvent] { + return hook.NewTaggedHook(app.onRecordAfterUpdateRequest, tags...) } -func (app *BaseApp) OnRecordBeforeDeleteRequest() *hook.Hook[*RecordDeleteEvent] { - return app.onRecordBeforeDeleteRequest +func (app *BaseApp) OnRecordBeforeDeleteRequest(tags ...string) *hook.TaggedHook[*RecordDeleteEvent] { + return hook.NewTaggedHook(app.onRecordBeforeDeleteRequest, tags...) } -func (app *BaseApp) OnRecordAfterDeleteRequest() *hook.Hook[*RecordDeleteEvent] { - return app.onRecordAfterDeleteRequest +func (app *BaseApp) OnRecordAfterDeleteRequest(tags ...string) *hook.TaggedHook[*RecordDeleteEvent] { + return hook.NewTaggedHook(app.onRecordAfterDeleteRequest, tags...) } // ------------------------------------------------------------------- diff --git a/core/base_test.go b/core/base_test.go index e23b6aa94..917506582 100644 --- a/core/base_test.go +++ b/core/base_test.go @@ -182,238 +182,6 @@ func TestBaseAppGetters(t *testing.T) { 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) } - - 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) { diff --git a/core/db.go b/core/db.go index 0de4bde33..9da74c3e2 100644 --- a/core/db.go +++ b/core/db.go @@ -11,7 +11,7 @@ func initPragmas(db *dbx.DB) error { _, err := db.NewQuery(` PRAGMA busy_timeout = 10000; PRAGMA journal_mode = WAL; - PRAGMA journal_size_limit = 100000000; + PRAGMA journal_size_limit = 200000000; PRAGMA synchronous = NORMAL; PRAGMA foreign_keys = TRUE; `).Execute() diff --git a/core/events.go b/core/events.go index e8d8321cd..ab9534020 100644 --- a/core/events.go +++ b/core/events.go @@ -6,6 +6,7 @@ import ( "github.com/pocketbase/pocketbase/models/schema" "github.com/pocketbase/pocketbase/models/settings" "github.com/pocketbase/pocketbase/tools/auth" + "github.com/pocketbase/pocketbase/tools/hook" "github.com/pocketbase/pocketbase/tools/mailer" "github.com/pocketbase/pocketbase/tools/search" "github.com/pocketbase/pocketbase/tools/subscriptions" @@ -13,6 +14,28 @@ import ( "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 // ------------------------------------------------------------------- @@ -35,16 +58,32 @@ type ApiErrorEvent struct { // Model DAO events data // ------------------------------------------------------------------- +var _ hook.Tagger = (*ModelEvent)(nil) + type ModelEvent struct { Dao *daos.Dao 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 // ------------------------------------------------------------------- type MailerRecordEvent struct { + BaseCollectionEvent + MailClient mailer.Mailer Message *mailer.Message Record *models.Record @@ -104,28 +143,37 @@ type SettingsUpdateEvent struct { // ------------------------------------------------------------------- type RecordsListEvent struct { + BaseCollectionEvent + HttpContext echo.Context - Collection *models.Collection Records []*models.Record Result *search.Result } type RecordViewEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordCreateEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordUpdateEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordDeleteEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } @@ -135,6 +183,8 @@ type RecordDeleteEvent struct { // ------------------------------------------------------------------- type RecordAuthEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record Token string @@ -142,6 +192,8 @@ type RecordAuthEvent struct { } type RecordAuthWithPasswordEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record Identity string @@ -149,53 +201,73 @@ type RecordAuthWithPasswordEvent struct { } type RecordAuthWithOAuth2Event struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record OAuth2User *auth.AuthUser } type RecordAuthRefreshEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordRequestPasswordResetEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordConfirmPasswordResetEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordRequestVerificationEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordConfirmVerificationEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordRequestEmailChangeEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordConfirmEmailChangeEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record } type RecordListExternalAuthsEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record ExternalAuths []*models.ExternalAuth } type RecordUnlinkExternalAuthEvent struct { + BaseCollectionEvent + HttpContext echo.Context Record *models.Record ExternalAuth *models.ExternalAuth @@ -270,23 +342,27 @@ type CollectionsListEvent struct { } type CollectionViewEvent struct { + BaseCollectionEvent + HttpContext echo.Context - Collection *models.Collection } type CollectionCreateEvent struct { + BaseCollectionEvent + HttpContext echo.Context - Collection *models.Collection } type CollectionUpdateEvent struct { + BaseCollectionEvent + HttpContext echo.Context - Collection *models.Collection } type CollectionDeleteEvent struct { + BaseCollectionEvent + HttpContext echo.Context - Collection *models.Collection } type CollectionsImportEvent struct { @@ -299,8 +375,9 @@ type CollectionsImportEvent struct { // ------------------------------------------------------------------- type FileDownloadEvent struct { + BaseCollectionEvent + HttpContext echo.Context - Collection *models.Collection Record *models.Record FileField *schema.SchemaField ServedPath string diff --git a/core/events_test.go b/core/events_test.go new file mode 100644 index 000000000..d4ee5ebc2 --- /dev/null +++ b/core/events_test.go @@ -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) + } + } + } +} diff --git a/mails/admin.go b/mails/admin.go index c419c10bf..2de8d051c 100644 --- a/mails/admin.go +++ b/mails/admin.go @@ -61,12 +61,11 @@ func SendAdminPasswordReset(app core.App, admin *models.Admin) error { HTML: body, } - event := &core.MailerAdminEvent{ - MailClient: mailClient, - Message: message, - Admin: admin, - Meta: map[string]any{"token": token}, - } + event := new(core.MailerAdminEvent) + event.MailClient = mailClient + event.Message = message + event.Admin = admin + event.Meta = map[string]any{"token": token} sendErr := app.OnMailerBeforeAdminResetPasswordSend().Trigger(event, func(e *core.MailerAdminEvent) error { return e.MailClient.Send(e.Message) diff --git a/mails/record.go b/mails/record.go index 481f25946..7caebbd76 100644 --- a/mails/record.go +++ b/mails/record.go @@ -37,12 +37,12 @@ func SendRecordPasswordReset(app core.App, authRecord *models.Record) error { HTML: body, } - event := &core.MailerRecordEvent{ - MailClient: mailClient, - Message: message, - Record: authRecord, - Meta: map[string]any{"token": token}, - } + event := new(core.MailerRecordEvent) + event.MailClient = mailClient + event.Message = message + event.Collection = authRecord.Collection() + event.Record = authRecord + event.Meta = map[string]any{"token": token} sendErr := app.OnMailerBeforeRecordResetPasswordSend().Trigger(event, func(e *core.MailerRecordEvent) error { return e.MailClient.Send(e.Message) @@ -81,12 +81,12 @@ func SendRecordVerification(app core.App, authRecord *models.Record) error { HTML: body, } - event := &core.MailerRecordEvent{ - MailClient: mailClient, - Message: message, - Record: authRecord, - Meta: map[string]any{"token": token}, - } + event := new(core.MailerRecordEvent) + event.MailClient = mailClient + event.Message = message + event.Collection = authRecord.Collection() + event.Record = authRecord + event.Meta = map[string]any{"token": token} sendErr := app.OnMailerBeforeRecordVerificationSend().Trigger(event, func(e *core.MailerRecordEvent) error { return e.MailClient.Send(e.Message) @@ -125,14 +125,14 @@ func SendRecordChangeEmail(app core.App, record *models.Record, newEmail string) HTML: body, } - event := &core.MailerRecordEvent{ - MailClient: mailClient, - Message: message, - Record: record, - Meta: map[string]any{ - "token": token, - "newEmail": newEmail, - }, + event := new(core.MailerRecordEvent) + event.MailClient = mailClient + event.Message = message + event.Collection = record.Collection() + event.Record = record + event.Meta = map[string]any{ + "token": token, + "newEmail": newEmail, } sendErr := app.OnMailerBeforeRecordChangeEmailSend().Trigger(event, func(e *core.MailerRecordEvent) error { diff --git a/tools/hook/hook_test.go b/tools/hook/hook_test.go index 11fd3a027..cdba3b5d1 100644 --- a/tools/hook/hook_test.go +++ b/tools/hook/hook_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func TestAddAndPreAdd(t *testing.T) { +func TestHookAddAndPreAdd(t *testing.T) { h := Hook[int]{} 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.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") 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 f1 := func(data int) error { called1 = true; return nil } diff --git a/tools/hook/tagged.go b/tools/hook/tagged.go new file mode 100644 index 000000000..35906947e --- /dev/null +++ b/tools/hook/tagged.go @@ -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 + }) +} diff --git a/tools/hook/tagged_test.go b/tools/hook/tagged_test.go new file mode 100644 index 000000000..84b2fa44f --- /dev/null +++ b/tools/hook/tagged_test.go @@ -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) + } + } +}