added additional godoc and updated the OAuth2 form to use the same created record pointer

This commit is contained in:
Gani Georgiev 2024-10-24 08:37:22 +03:00
parent c41a4dfc07
commit 0b7741f1f7
28 changed files with 4020 additions and 3750 deletions

View File

@ -29,14 +29,14 @@ func bindBatchApi(app core.App, rg *router.RouterGroup[*core.RequestEvent]) {
type HandleFunc func(e *core.RequestEvent) error type HandleFunc func(e *core.RequestEvent) error
type BatchActionHandlerFunc func(app core.App, ir *core.InternalRequest, params map[string]string, next func() error) HandleFunc type BatchActionHandlerFunc func(app core.App, ir *core.InternalRequest, params map[string]string, next func(data any) error) HandleFunc
// ValidBatchActions defines a map with the supported batch InternalRequest actions. // ValidBatchActions defines a map with the supported batch InternalRequest actions.
// //
// Note: when adding new routes make sure that their middlewares are inlined! // Note: when adding new routes make sure that their middlewares are inlined!
var ValidBatchActions = map[*regexp.Regexp]BatchActionHandlerFunc{ var ValidBatchActions = map[*regexp.Regexp]BatchActionHandlerFunc{
// "upsert" handler // "upsert" handler
regexp.MustCompile(`^PUT /api/collections/(?P<collection>[^\/\?]+)/records(?P<query>\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func() error) HandleFunc { regexp.MustCompile(`^PUT /api/collections/(?P<collection>[^\/\?]+)/records(?P<query>\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func(any) error) HandleFunc {
var id string var id string
if len(ir.Body) > 0 && ir.Body["id"] != "" { if len(ir.Body) > 0 && ir.Body["id"] != "" {
id = cast.ToString(ir.Body["id"]) id = cast.ToString(ir.Body["id"])
@ -59,13 +59,13 @@ var ValidBatchActions = map[*regexp.Regexp]BatchActionHandlerFunc{
ir.URL = "/api/collections/" + params["collection"] + "/records" + params["query"] ir.URL = "/api/collections/" + params["collection"] + "/records" + params["query"]
return recordCreate(next) return recordCreate(next)
}, },
regexp.MustCompile(`^POST /api/collections/(?P<collection>[^\/\?]+)/records(\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func() error) HandleFunc { regexp.MustCompile(`^POST /api/collections/(?P<collection>[^\/\?]+)/records(\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func(any) error) HandleFunc {
return recordCreate(next) return recordCreate(next)
}, },
regexp.MustCompile(`^PATCH /api/collections/(?P<collection>[^\/\?]+)/records/(?P<id>[^\/\?]+)(\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func() error) HandleFunc { regexp.MustCompile(`^PATCH /api/collections/(?P<collection>[^\/\?]+)/records/(?P<id>[^\/\?]+)(\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func(any) error) HandleFunc {
return recordUpdate(next) return recordUpdate(next)
}, },
regexp.MustCompile(`^DELETE /api/collections/(?P<collection>[^\/\?]+)/records/(?P<id>[^\/\?]+)(\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func() error) HandleFunc { regexp.MustCompile(`^DELETE /api/collections/(?P<collection>[^\/\?]+)/records/(?P<id>[^\/\?]+)(\?.*)?$`): func(app core.App, ir *core.InternalRequest, params map[string]string, next func(any) error) HandleFunc {
return recordDelete(next) return recordDelete(next)
}, },
} }
@ -246,7 +246,7 @@ func (p *batchProcessor) process(activeApp core.App, batch []*core.InternalReque
p.baseEvent, p.baseEvent,
batch[0], batch[0],
p.infoContext, p.infoContext,
func() error { func(_ any) error {
if len(batch) == 1 { if len(batch) == 1 {
return nil return nil
} }
@ -277,7 +277,7 @@ func processInternalRequest(
baseEvent *core.RequestEvent, baseEvent *core.RequestEvent,
ir *core.InternalRequest, ir *core.InternalRequest,
infoContext string, infoContext string,
optNext func() error, optNext func(data any) error,
) (*BatchRequestResult, error) { ) (*BatchRequestResult, error) {
handle, params, ok := prepareInternalAction(activeApp, ir, optNext) handle, params, ok := prepareInternalAction(activeApp, ir, optNext)
if !ok { if !ok {
@ -475,7 +475,7 @@ func extractPrefixedFiles(request *http.Request, prefixes ...string) (map[string
return result, nil return result, nil
} }
func prepareInternalAction(activeApp core.App, ir *core.InternalRequest, optNext func() error) (HandleFunc, map[string]string, bool) { func prepareInternalAction(activeApp core.App, ir *core.InternalRequest, optNext func(data any) error) (HandleFunc, map[string]string, bool) {
full := strings.ToUpper(ir.Method) + " " + ir.URL full := strings.ToUpper(ir.Method) + " " + ir.URL
for re, actionFactory := range ValidBatchActions { for re, actionFactory := range ValidBatchActions {

View File

@ -60,6 +60,7 @@ func (api *fileApi) fileToken(e *core.RequestEvent) error {
event := new(core.FileTokenRequestEvent) event := new(core.FileTokenRequestEvent)
event.RequestEvent = e event.RequestEvent = e
event.Token = token event.Token = token
event.Record = e.Auth
return e.App.OnFileTokenRequest().Trigger(event, func(e *core.FileTokenRequestEvent) error { return e.App.OnFileTokenRequest().Trigger(event, func(e *core.FileTokenRequestEvent) error {
return e.JSON(http.StatusOK, map[string]string{ return e.JSON(http.StatusOK, map[string]string{

View File

@ -2,7 +2,6 @@ package apis
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
@ -335,27 +334,19 @@ func sendOAuth2RecordCreateRequest(txApp core.App, e *core.RecordAuthWithOAuth2R
Body: payload, Body: payload,
} }
response, err := processInternalRequest(txApp, e.RequestEvent, ir, core.RequestInfoContextOAuth2, nil) var createdRecord *core.Record
response, err := processInternalRequest(txApp, e.RequestEvent, ir, core.RequestInfoContextOAuth2, func(data any) error {
createdRecord, _ = data.(*core.Record)
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if response.Status != http.StatusOK { if response.Status != http.StatusOK || createdRecord == nil {
return nil, errors.New("failed to create OAuth2 auth record") return nil, errors.New("failed to create OAuth2 auth record")
} }
recordResponse := struct { return createdRecord, nil
Id string `json:"id"`
}{}
raw, err := json.Marshal(response.Body)
if err != nil {
return nil, err
}
if err = json.Unmarshal(raw, &recordResponse); err != nil {
return nil, err
}
return txApp.FindRecordById(e.Collection, recordResponse.Id)
} }

View File

@ -147,7 +147,7 @@ func recordView(e *core.RequestEvent) error {
}) })
} }
func recordCreate(optFinalizer func() error) func(e *core.RequestEvent) error { func recordCreate(optFinalizer func(data any) error) func(e *core.RequestEvent) error {
return func(e *core.RequestEvent) error { return func(e *core.RequestEvent) error {
collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue("collection")) collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue("collection"))
if err != nil || collection == nil { if err != nil || collection == nil {
@ -278,7 +278,7 @@ func recordCreate(optFinalizer func() error) func(e *core.RequestEvent) error {
if optFinalizer != nil { if optFinalizer != nil {
isOptFinalizerCalled = true isOptFinalizerCalled = true
err = optFinalizer() err = optFinalizer(e.Record)
if err != nil { if err != nil {
return firstApiError(err, e.InternalServerError("", err)) return firstApiError(err, e.InternalServerError("", err))
} }
@ -292,7 +292,7 @@ func recordCreate(optFinalizer func() error) func(e *core.RequestEvent) error {
// e.g. in case the regular hook chain was stopped and the finalizer cannot be executed as part of the last e.Next() task // e.g. in case the regular hook chain was stopped and the finalizer cannot be executed as part of the last e.Next() task
if !isOptFinalizerCalled && optFinalizer != nil { if !isOptFinalizerCalled && optFinalizer != nil {
if err := optFinalizer(); err != nil { if err := optFinalizer(event.Record); err != nil {
return firstApiError(err, e.InternalServerError("", err)) return firstApiError(err, e.InternalServerError("", err))
} }
} }
@ -301,7 +301,7 @@ func recordCreate(optFinalizer func() error) func(e *core.RequestEvent) error {
} }
} }
func recordUpdate(optFinalizer func() error) func(e *core.RequestEvent) error { func recordUpdate(optFinalizer func(data any) error) func(e *core.RequestEvent) error {
return func(e *core.RequestEvent) error { return func(e *core.RequestEvent) error {
collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue("collection")) collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue("collection"))
if err != nil || collection == nil { if err != nil || collection == nil {
@ -404,7 +404,7 @@ func recordUpdate(optFinalizer func() error) func(e *core.RequestEvent) error {
if optFinalizer != nil { if optFinalizer != nil {
isOptFinalizerCalled = true isOptFinalizerCalled = true
err = optFinalizer() err = optFinalizer(e.Record)
if err != nil { if err != nil {
return firstApiError(err, e.InternalServerError("", fmt.Errorf("update optFinalizer error: %w", err))) return firstApiError(err, e.InternalServerError("", fmt.Errorf("update optFinalizer error: %w", err)))
} }
@ -418,7 +418,7 @@ func recordUpdate(optFinalizer func() error) func(e *core.RequestEvent) error {
// e.g. in case the regular hook chain was stopped and the finalizer cannot be executed as part of the last e.Next() task // e.g. in case the regular hook chain was stopped and the finalizer cannot be executed as part of the last e.Next() task
if !isOptFinalizerCalled && optFinalizer != nil { if !isOptFinalizerCalled && optFinalizer != nil {
if err := optFinalizer(); err != nil { if err := optFinalizer(event.Record); err != nil {
return firstApiError(err, e.InternalServerError("", fmt.Errorf("update optFinalizer error: %w", err))) return firstApiError(err, e.InternalServerError("", fmt.Errorf("update optFinalizer error: %w", err)))
} }
} }
@ -427,7 +427,7 @@ func recordUpdate(optFinalizer func() error) func(e *core.RequestEvent) error {
} }
} }
func recordDelete(optFinalizer func() error) func(e *core.RequestEvent) error { func recordDelete(optFinalizer func(data any) error) func(e *core.RequestEvent) error {
return func(e *core.RequestEvent) error { return func(e *core.RequestEvent) error {
collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue("collection")) collection, err := e.App.FindCachedCollectionByNameOrId(e.Request.PathValue("collection"))
if err != nil || collection == nil { if err != nil || collection == nil {
@ -494,7 +494,7 @@ func recordDelete(optFinalizer func() error) func(e *core.RequestEvent) error {
if optFinalizer != nil { if optFinalizer != nil {
isOptFinalizerCalled = true isOptFinalizerCalled = true
err = optFinalizer() err = optFinalizer(e.Record)
if err != nil { if err != nil {
return firstApiError(err, e.InternalServerError("", fmt.Errorf("delete optFinalizer error: %w", err))) return firstApiError(err, e.InternalServerError("", fmt.Errorf("delete optFinalizer error: %w", err)))
} }
@ -508,7 +508,7 @@ func recordDelete(optFinalizer func() error) func(e *core.RequestEvent) error {
// e.g. in case the regular hook chain was stopped and the finalizer cannot be executed as part of the last e.Next() task // e.g. in case the regular hook chain was stopped and the finalizer cannot be executed as part of the last e.Next() task
if !isOptFinalizerCalled && optFinalizer != nil { if !isOptFinalizerCalled && optFinalizer != nil {
if err := optFinalizer(); err != nil { if err := optFinalizer(event.Record); err != nil {
return firstApiError(err, e.InternalServerError("", fmt.Errorf("delete optFinalizer error: %w", err))) return firstApiError(err, e.InternalServerError("", fmt.Errorf("delete optFinalizer error: %w", err)))
} }
} }

View File

@ -152,6 +152,7 @@ func TestEnrichRecords(t *testing.T) {
`"test@example.com"`, `"test@example.com"`,
`"expand":{"rel_many"`, `"expand":{"rel_many"`,
`"id":"bgs820n361vj1qd"`, `"id":"bgs820n361vj1qd"`,
`"id":"4q1xlclmfloku33"`,
`"id":"oap640cot4yru2s"`, `"id":"oap640cot4yru2s"`,
}, },
notExpected: []string{ notExpected: []string{

View File

@ -596,7 +596,7 @@ type App interface {
// CanAccessRecord checks if a record is allowed to be accessed by the // CanAccessRecord checks if a record is allowed to be accessed by the
// specified requestInfo and accessRule. // specified requestInfo and accessRule.
// //
// Rule and db checks are ignored in case requestInfo.AuthRecord is a superuser. // Rule and db checks are ignored in case requestInfo.Auth is a superuser.
// //
// The returned error indicate that something unexpected happened during // The returned error indicate that something unexpected happened during
// the check (eg. invalid rule or db query error). // the check (eg. invalid rule or db query error).
@ -633,17 +633,19 @@ type App interface {
// App event hooks // App event hooks
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnBootstrap hook is triggered on initializing the main application // OnBootstrap hook is triggered when initializing the main application
// resources (db, app settings, etc). // resources (db, app settings, etc).
OnBootstrap() *hook.Hook[*BootstrapEvent] OnBootstrap() *hook.Hook[*BootstrapEvent]
// OnServe hook is triggered on when the app web server is started // OnServe hook is triggered when the app web server is started
// (after starting the tcp listener but before initializing the blocking serve task), // (after starting the TCP listener but before initializing the blocking serve task),
// allowing you to adjust its options and attach new routes or middlewares. // allowing you to adjust its options and attach new routes or middlewares.
OnServe() *hook.Hook[*ServeEvent] OnServe() *hook.Hook[*ServeEvent]
// OnTerminate hook is triggered when the app is in the process // OnTerminate hook is triggered when the app is in the process
// of being terminated (ex. on SIGTERM signal). // of being terminated (ex. on SIGTERM signal).
//
// Note that the app could be terminated abruptly without awaiting the hook completion.
OnTerminate() *hook.Hook[*TerminateEvent] OnTerminate() *hook.Hook[*TerminateEvent]
// OnBackupCreate hook is triggered on each [App.CreateBackup] call. // OnBackupCreate hook is triggered on each [App.CreateBackup] call.
@ -661,6 +663,9 @@ type App interface {
// OnModelValidate is triggered every time when a model is being validated // OnModelValidate is triggered every time when a model is being validated
// (e.g. triggered by App.Validate() or App.Save()). // (e.g. triggered by App.Validate() or App.Save()).
// //
// For convenience, if you want to listen to only the Record models
// events without doing manual type assertion, you can attach to the OnRecord* proxy hooks.
//
// If the optional "tags" list (Collection id/name, Model table name, etc.) is specified, // If the optional "tags" list (Collection id/name, Model table name, etc.) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
@ -680,7 +685,7 @@ type App interface {
// Note that successful execution doesn't guarantee that the model // Note that successful execution doesn't guarantee that the model
// is persisted in the database since its wrapping transaction may // is persisted in the database since its wrapping transaction may
// not have been committed yet. // not have been committed yet.
// If you wan to listen to only the actual persisted events, you can // If you want to listen to only the actual persisted events, you can
// bind to [OnModelAfterCreateSuccess] or [OnModelAfterCreateError] hooks. // bind to [OnModelAfterCreateSuccess] or [OnModelAfterCreateError] hooks.
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
@ -703,7 +708,7 @@ type App interface {
// Note that successful execution doesn't guarantee that the model // Note that successful execution doesn't guarantee that the model
// is persisted in the database since its wrapping transaction may have been // is persisted in the database since its wrapping transaction may have been
// committed yet. // committed yet.
// If you wan to listen to only the actual persisted events, // If you want to listen to only the actual persisted events,
// you can bind to [OnModelAfterCreateSuccess] or [OnModelAfterCreateError] hooks. // you can bind to [OnModelAfterCreateSuccess] or [OnModelAfterCreateError] hooks.
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
@ -718,7 +723,7 @@ type App interface {
// Model DB create persistence. // Model DB create persistence.
// //
// Note that when a Model is persisted as part of a transaction, // Note that when a Model is persisted as part of a transaction,
// this hook is triggered AFTER the transaction has been committed. // this hook is delayed and executed only AFTER the transaction has been committed.
// This hook is NOT triggered in case the transaction rollbacks // This hook is NOT triggered in case the transaction rollbacks
// (aka. when the model wasn't persisted). // (aka. when the model wasn't persisted).
// //
@ -732,10 +737,11 @@ type App interface {
// OnModelAfterCreateError is triggered after each failed // OnModelAfterCreateError is triggered after each failed
// Model DB create persistence. // Model DB create persistence.
// Note that when a Model is persisted as part of a transaction, //
// this hook is triggered in one of the following cases: // Note that the execution of this hook is either immediate or delayed
// - immediately after App.Save() failure // depending on the error:
// - on transaction rollback // - "immediate" on App.Save() failure
// - "delayed" on transaction rollback
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
// events without doing manual type assertion, you can attach to the OnRecord* proxy hooks. // events without doing manual type assertion, you can attach to the OnRecord* proxy hooks.
@ -759,7 +765,7 @@ type App interface {
// Note that successful execution doesn't guarantee that the model // Note that successful execution doesn't guarantee that the model
// is persisted in the database since its wrapping transaction may // is persisted in the database since its wrapping transaction may
// not have been committed yet. // not have been committed yet.
// If you wan to listen to only the actual persisted events, you can // If you want to listen to only the actual persisted events, you can
// bind to [OnModelAfterUpdateSuccess] or [OnModelAfterUpdateError] hooks. // bind to [OnModelAfterUpdateSuccess] or [OnModelAfterUpdateError] hooks.
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
@ -782,7 +788,7 @@ type App interface {
// Note that successful execution doesn't guarantee that the model // Note that successful execution doesn't guarantee that the model
// is persisted in the database since its wrapping transaction may have been // is persisted in the database since its wrapping transaction may have been
// committed yet. // committed yet.
// If you wan to listen to only the actual persisted events, // If you want to listen to only the actual persisted events,
// you can bind to [OnModelAfterUpdateSuccess] or [OnModelAfterUpdateError] hooks. // you can bind to [OnModelAfterUpdateSuccess] or [OnModelAfterUpdateError] hooks.
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
@ -797,7 +803,7 @@ type App interface {
// Model DB update persistence. // Model DB update persistence.
// //
// Note that when a Model is persisted as part of a transaction, // Note that when a Model is persisted as part of a transaction,
// this hook is triggered AFTER the transaction has been committed. // this hook is delayed and executed only AFTER the transaction has been committed.
// This hook is NOT triggered in case the transaction rollbacks // This hook is NOT triggered in case the transaction rollbacks
// (aka. when the model changes weren't persisted). // (aka. when the model changes weren't persisted).
// //
@ -812,10 +818,10 @@ type App interface {
// OnModelAfterUpdateError is triggered after each failed // OnModelAfterUpdateError is triggered after each failed
// Model DB update persistence. // Model DB update persistence.
// //
// Note that when a Model is persisted as part of a transaction, // Note that the execution of this hook is either immediate or delayed
// this hook is triggered in one of the following cases: // depending on the error:
// - immediately after App.Save() failure // - "immediate" on App.Save() failure
// - on transaction rollback // - "delayed" on transaction rollback
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
// events without doing manual type assertion, you can attach to the OnRecord* proxy hooks. // events without doing manual type assertion, you can attach to the OnRecord* proxy hooks.
@ -833,7 +839,7 @@ type App interface {
// Note that successful execution doesn't guarantee that the model // Note that successful execution doesn't guarantee that the model
// is deleted from the database since its wrapping transaction may // is deleted from the database since its wrapping transaction may
// not have been committed yet. // not have been committed yet.
// If you wan to listen to only the actual persisted deleted events, you can // If you want to listen to only the actual persisted deleted events, you can
// bind to [OnModelAfterDeleteSuccess] or [OnModelAfterDeleteError] hooks. // bind to [OnModelAfterDeleteSuccess] or [OnModelAfterDeleteError] hooks.
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
@ -856,7 +862,7 @@ type App interface {
// Note that successful execution doesn't guarantee that the model // Note that successful execution doesn't guarantee that the model
// is deleted from the database since its wrapping transaction may // is deleted from the database since its wrapping transaction may
// not have been committed yet. // not have been committed yet.
// If you wan to listen to only the actual persisted deleted events, you can // If you want to listen to only the actual persisted deleted events, you can
// bind to [OnModelAfterDeleteSuccess] or [OnModelAfterDeleteError] hooks. // bind to [OnModelAfterDeleteSuccess] or [OnModelAfterDeleteError] hooks.
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
@ -871,7 +877,7 @@ type App interface {
// Model DB delete persistence. // Model DB delete persistence.
// //
// Note that when a Model is deleted as part of a transaction, // Note that when a Model is deleted as part of a transaction,
// this hook is triggered AFTER the transaction has been committed. // this hook is delayed and executed only AFTER the transaction has been committed.
// This hook is NOT triggered in case the transaction rollbacks // This hook is NOT triggered in case the transaction rollbacks
// (aka. when the model delete wasn't persisted). // (aka. when the model delete wasn't persisted).
// //
@ -886,10 +892,10 @@ type App interface {
// OnModelAfterDeleteError is triggered after each failed // OnModelAfterDeleteError is triggered after each failed
// Model DB delete persistence. // Model DB delete persistence.
// //
// Note that when a Model is deleted as part of a transaction, // Note that the execution of this hook is either immediate or delayed
// this hook is triggered in one of the following cases: // depending on the error:
// - immediately after App.Delete() failure // - "immediate" on App.Delete() failure
// - on transaction rollback // - "delayed" on transaction rollback
// //
// For convenience, if you want to listen to only the Record models // For convenience, if you want to listen to only the Record models
// events without doing manual type assertion, you can attach to the OnRecord* proxy hooks. // events without doing manual type assertion, you can attach to the OnRecord* proxy hooks.
@ -904,10 +910,9 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnRecordEnrich is triggered every time when a record is enriched // OnRecordEnrich is triggered every time when a record is enriched
// (during realtime message seriazation, as part of the builtin Record // (as part of the builtin Record responses, during realtime message seriazation, or when [apis.EnrichRecord] is invoked).
// responses, or when [apis.EnrichRecord] is invoked).
// //
// It could be used for example to redact/hide or add computed temp // It could be used for example to redact/hide or add computed temporary
// Record model props only for the specific request info. For example: // Record model props only for the specific request info. For example:
// //
// app.OnRecordEnrich("posts").BindFunc(func(e core.*RecordEnrichEvent) { // app.OnRecordEnrich("posts").BindFunc(func(e core.*RecordEnrichEvent) {
@ -928,7 +933,7 @@ type App interface {
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordEnrich(tags ...string) *hook.TaggedHook[*RecordEnrichEvent] OnRecordEnrich(tags ...string) *hook.TaggedHook[*RecordEnrichEvent]
// OnRecordValidate is a proxy Record model hook for [OnModelValidate]. // OnRecordValidate is a Record proxy model hook of [OnModelValidate].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -937,28 +942,28 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnRecordCreate is a proxy Record model hook for [OnModelCreate]. // OnRecordCreate is a Record proxy model hook of [OnModelCreate].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordCreate(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordCreate(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordCreateExecute is a proxy Record model hook for [OnModelCreateExecute]. // OnRecordCreateExecute is a Record proxy model hook of [OnModelCreateExecute].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordCreateExecute(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordCreateExecute(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordAfterCreateSuccess is a proxy Record model hook for [OnModelAfterCreateSuccess]. // OnRecordAfterCreateSuccess is a Record proxy model hook of [OnModelAfterCreateSuccess].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordAfterCreateSuccess(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordAfterCreateSuccess(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordAfterCreateError is a proxy Record model hook for [OnModelAfterCreateError]. // OnRecordAfterCreateError is a Record proxy model hook of [OnModelAfterCreateError].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -967,28 +972,28 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnRecordUpdate is a proxy Record model hook for [OnModelUpdate]. // OnRecordUpdate is a Record proxy model hook of [OnModelUpdate].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordUpdate(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordUpdate(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordUpdateExecute is a proxy Record model hook for [OnModelUpdateExecute]. // OnRecordUpdateExecute is a Record proxy model hook of [OnModelUpdateExecute].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordUpdateExecute(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordUpdateExecute(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordAfterUpdateSuccess is a proxy Record model hook for [OnModelAfterUpdateSuccess]. // OnRecordAfterUpdateSuccess is a Record proxy model hook of [OnModelAfterUpdateSuccess].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordAfterUpdateSuccess(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordAfterUpdateSuccess(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordAfterUpdateError is a proxy Record model hook for [OnModelAfterUpdateError]. // OnRecordAfterUpdateError is a Record proxy model hook of [OnModelAfterUpdateError].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -997,28 +1002,28 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnRecordDelete is a proxy Record model hook for [OnModelDelete]. // OnRecordDelete is a Record proxy model hook of [OnModelDelete].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordDelete(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordDelete(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordDeleteExecute is a proxy Record model hook for [OnModelDeleteExecute]. // OnRecordDeleteExecute is a Record proxy model hook of [OnModelDeleteExecute].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordDeleteExecute(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordDeleteExecute(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordAfterDeleteSuccess is a proxy Record model hook for [OnModelAfterDeleteSuccess]. // OnRecordAfterDeleteSuccess is a Record proxy model hook of [OnModelAfterDeleteSuccess].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnRecordAfterDeleteSuccess(tags ...string) *hook.TaggedHook[*RecordEvent] OnRecordAfterDeleteSuccess(tags ...string) *hook.TaggedHook[*RecordEvent]
// OnRecordAfterDeleteError is a proxy Record model hook for [OnModelAfterDeleteError]. // OnRecordAfterDeleteError is a Record proxy model hook of [OnModelAfterDeleteError].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -1029,7 +1034,7 @@ type App interface {
// Collection models event hooks // Collection models event hooks
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnCollectionValidate is a proxy Collection model hook for [OnModelValidate]. // OnCollectionValidate is a Collection proxy model hook of [OnModelValidate].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -1038,28 +1043,28 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnCollectionCreate is a proxy Collection model hook for [OnModelCreate]. // OnCollectionCreate is a Collection proxy model hook of [OnModelCreate].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionCreate(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionCreate(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionCreateExecute is a proxy Collection model hook for [OnModelCreateExecute]. // OnCollectionCreateExecute is a Collection proxy model hook of [OnModelCreateExecute].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionCreateExecute(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionCreateExecute(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionAfterCreateSuccess is a proxy Collection model hook for [OnModelAfterCreateSuccess]. // OnCollectionAfterCreateSuccess is a Collection proxy model hook of [OnModelAfterCreateSuccess].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionAfterCreateSuccess(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionAfterCreateSuccess(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionAfterCreateError is a proxy Collection model hook for [OnModelAfterCreateError]. // OnCollectionAfterCreateError is a Collection proxy model hook of [OnModelAfterCreateError].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -1068,28 +1073,28 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnCollectionUpdate is a proxy Collection model hook for [OnModelUpdate]. // OnCollectionUpdate is a Collection proxy model hook of [OnModelUpdate].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionUpdate(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionUpdate(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionUpdateExecute is a proxy Collection model hook for [OnModelUpdateExecute]. // OnCollectionUpdateExecute is a Collection proxy model hook of [OnModelUpdateExecute].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionUpdateExecute(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionUpdateExecute(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionAfterUpdateSuccess is a proxy Collection model hook for [OnModelAfterUpdateSuccess]. // OnCollectionAfterUpdateSuccess is a Collection proxy model hook of [OnModelAfterUpdateSuccess].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionAfterUpdateSuccess(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionAfterUpdateSuccess(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionAfterUpdateError is a proxy Collection model hook for [OnModelAfterUpdateError]. // OnCollectionAfterUpdateError is a Collection proxy model hook of [OnModelAfterUpdateError].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -1098,28 +1103,28 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnCollectionDelete is a proxy Collection model hook for [OnModelDelete]. // OnCollectionDelete is a Collection proxy model hook of [OnModelDelete].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionDelete(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionDelete(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionDeleteExecute is a proxy Collection model hook for [OnModelDeleteExecute]. // OnCollectionDeleteExecute is a Collection proxy model hook of [OnModelDeleteExecute].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionDeleteExecute(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionDeleteExecute(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionAfterDeleteSuccess is a proxy Collection model hook for [OnModelAfterDeleteSuccess]. // OnCollectionAfterDeleteSuccess is a Collection proxy model hook of [OnModelAfterDeleteSuccess].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags. // triggered and called only if their event data origin matches the tags.
OnCollectionAfterDeleteSuccess(tags ...string) *hook.TaggedHook[*CollectionEvent] OnCollectionAfterDeleteSuccess(tags ...string) *hook.TaggedHook[*CollectionEvent]
// OnCollectionAfterDeleteError is a proxy Collection model hook for [OnModelAfterDeleteError]. // OnCollectionAfterDeleteError is a Collection proxy model hook of [OnModelAfterDeleteError].
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -1131,7 +1136,7 @@ type App interface {
// --------------------------------------------------------------- // ---------------------------------------------------------------
// OnMailerSend hook is triggered every time when a new email is // OnMailerSend hook is triggered every time when a new email is
// being send using the App.NewMailClient() instance. // being send using the [App.NewMailClient()] instance.
// //
// It allows intercepting the email message or to use a custom mailer client. // It allows intercepting the email message or to use a custom mailer client.
OnMailerSend() *hook.Hook[*MailerEvent] OnMailerSend() *hook.Hook[*MailerEvent]
@ -1187,7 +1192,7 @@ type App interface {
// OnRealtimeConnectRequest hook is triggered when establishing the SSE client connection. // OnRealtimeConnectRequest hook is triggered when establishing the SSE client connection.
// //
// Any execution after [e.Next()] of a hook handler happens after the client disconnects. // Any execution after e.Next() of a hook handler happens after the client disconnects.
OnRealtimeConnectRequest() *hook.Hook[*RealtimeConnectRequestEvent] OnRealtimeConnectRequest() *hook.Hook[*RealtimeConnectRequestEvent]
// OnRealtimeMessageSend hook is triggered when sending an SSE message to a client. // OnRealtimeMessageSend hook is triggered when sending an SSE message to a client.
@ -1216,7 +1221,7 @@ type App interface {
// OnSettingsReload hook is triggered every time when the App.Settings() // OnSettingsReload hook is triggered every time when the App.Settings()
// is being replaced with a new state. // is being replaced with a new state.
// //
// Calling App.Settings() after e.Next() should return the new state. // Calling App.Settings() after e.Next() returns the new state.
OnSettingsReload() *hook.Hook[*SettingsReloadEvent] OnSettingsReload() *hook.Hook[*SettingsReloadEvent]
// --------------------------------------------------------------- // ---------------------------------------------------------------
@ -1229,8 +1234,12 @@ type App interface {
// returning it to the client. // returning it to the client.
OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*FileDownloadRequestEvent] OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*FileDownloadRequestEvent]
// OnFileBeforeTokenRequest hook is triggered on each file token API request. // OnFileBeforeTokenRequest hook is triggered on each auth file token API request.
OnFileTokenRequest() *hook.Hook[*FileTokenRequestEvent] //
// If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be
// triggered and called only if their event data origin matches the tags.
OnFileTokenRequest(tags ...string) *hook.TaggedHook[*FileTokenRequestEvent]
// --------------------------------------------------------------- // ---------------------------------------------------------------
// Record Auth API event hooks // Record Auth API event hooks
@ -1250,9 +1259,8 @@ type App interface {
// OnRecordAuthWithPasswordRequest hook is triggered on each // OnRecordAuthWithPasswordRequest hook is triggered on each
// Record auth with password API request. // Record auth with password API request.
// //
// RecordAuthWithPasswordRequestEvent.Record could be nil if no // [RecordAuthWithPasswordRequestEvent.Record] could be nil if no matching identity is found, allowing
// matching identity is found, allowing you to manually locate a different // you to manually locate a different Record model (by reassigning [RecordAuthWithPasswordRequestEvent.Record]).
// Record model (by reassigning [RecordAuthWithPasswordRequestEvent.Record]).
// //
// If the optional "tags" list (Collection ids or names) is specified, // If the optional "tags" list (Collection ids or names) is specified,
// then all event handlers registered via the created hook will be // then all event handlers registered via the created hook will be
@ -1262,7 +1270,7 @@ type App interface {
// OnRecordAuthWithOAuth2Request hook is triggered on each Record // OnRecordAuthWithOAuth2Request hook is triggered on 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).
// //
// If the [RecordAuthWithOAuth2RequestEvent.Record] is not set, then the OAuth2 // If [RecordAuthWithOAuth2RequestEvent.Record] is not set, then the OAuth2
// request will try to create a new auth Record. // request will try to create a new auth Record.
// //
// To assign or link a different existing record model you can // To assign or link a different existing record model you can

View File

@ -981,8 +981,8 @@ func (app *BaseApp) OnFileDownloadRequest(tags ...string) *hook.TaggedHook[*File
return hook.NewTaggedHook(app.onFileDownloadRequest, tags...) return hook.NewTaggedHook(app.onFileDownloadRequest, tags...)
} }
func (app *BaseApp) OnFileTokenRequest() *hook.Hook[*FileTokenRequestEvent] { func (app *BaseApp) OnFileTokenRequest(tags ...string) *hook.TaggedHook[*FileTokenRequestEvent] {
return app.onFileTokenRequest return hook.NewTaggedHook(app.onFileTokenRequest, tags...)
} }
// ------------------------------------------------------------------- // -------------------------------------------------------------------

View File

@ -325,9 +325,12 @@ type baseCollection struct {
Type string `db:"type" json:"type" form:"type"` Type string `db:"type" json:"type" form:"type"`
Fields FieldsList `db:"fields" json:"fields" form:"fields"` Fields FieldsList `db:"fields" json:"fields" form:"fields"`
Indexes types.JSONArray[string] `db:"indexes" json:"indexes" form:"indexes"` Indexes types.JSONArray[string] `db:"indexes" json:"indexes" form:"indexes"`
System bool `db:"system" json:"system" form:"system"`
Created types.DateTime `db:"created" json:"created"` Created types.DateTime `db:"created" json:"created"`
Updated types.DateTime `db:"updated" json:"updated"` Updated types.DateTime `db:"updated" json:"updated"`
// System prevents the collection rename, deletion and rules change.
// It is used primarily for internal purposes for collections like "_superusers", "_externalAuths", etc.
System bool `db:"system" json:"system" form:"system"`
} }
// Collection defines the table, fields and various options related to a set of records. // Collection defines the table, fields and various options related to a set of records.

View File

@ -4,9 +4,11 @@ import (
"net/http" "net/http"
validation "github.com/go-ozzo/ozzo-validation/v4" validation "github.com/go-ozzo/ozzo-validation/v4"
"github.com/pocketbase/pocketbase/tools/hook"
) )
type BatchRequestEvent struct { type BatchRequestEvent struct {
hook.Event
*RequestEvent *RequestEvent
Batch []*InternalRequest Batch []*InternalRequest

View File

@ -323,6 +323,7 @@ func syncModelErrorEventWithCollectionErrorEvent(me *ModelErrorEvent, ce *Collec
type FileTokenRequestEvent struct { type FileTokenRequestEvent struct {
hook.Event hook.Event
*RequestEvent *RequestEvent
baseRecordEventData
Token string Token string
} }

View File

@ -25,12 +25,26 @@ var (
// AutodateField defines an "autodate" type field, aka. // AutodateField defines an "autodate" type field, aka.
// field which datetime value could be auto set on record create/update. // field which datetime value could be auto set on record create/update.
// //
// This field is usually used for defining timestamp fields like "created" and "updated".
//
// Requires either both or at least one of the OnCreate or OnUpdate options to be set. // Requires either both or at least one of the OnCreate or OnUpdate options to be set.
type AutodateField struct { type AutodateField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -19,11 +19,25 @@ const FieldTypeBool = "bool"
var _ Field = (*BoolField)(nil) var _ Field = (*BoolField)(nil)
// BoolField defines "bool" type field to store a single true/false value. // BoolField defines "bool" type field to store a single true/false value.
//
// The respective zero record field value is false.
type BoolField struct { type BoolField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -19,11 +19,25 @@ const FieldTypeDate = "date"
var _ Field = (*DateField)(nil) var _ Field = (*DateField)(nil)
// DateField defines "date" type field to store a single [types.DateTime] value. // DateField defines "date" type field to store a single [types.DateTime] value.
//
// The respective zero record field value is the zero [types.DateTime].
type DateField struct { type DateField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -25,11 +25,25 @@ var (
) )
// EditorField defines "editor" type field to store HTML formatted text. // EditorField defines "editor" type field to store HTML formatted text.
//
// The respective zero record field value is empty string.
type EditorField struct { type EditorField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -22,11 +22,25 @@ const FieldTypeEmail = "email"
var _ Field = (*EmailField)(nil) var _ Field = (*EmailField)(nil)
// EmailField defines "email" type field for storing single email string address. // EmailField defines "email" type field for storing single email string address.
//
// The respective zero record field value is empty string.
type EmailField struct { type EmailField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -53,6 +53,8 @@ var (
// //
// If MaxSelect is > 1, then the field value is expected to be a slice of record ids. // If MaxSelect is > 1, then the field value is expected to be a slice of record ids.
// //
// The respective zero record field value is either empty string (single) or empty string slice (multiple).
//
// --- // ---
// //
// The following additional setter keys are available: // The following additional setter keys are available:
@ -72,10 +74,22 @@ var (
// // []string{"old2.txt",} // // []string{"old2.txt",}
// record.Set("documents-", "old1.txt") // record.Set("documents-", "old1.txt")
type FileField struct { type FileField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -29,11 +29,25 @@ var (
) )
// JSONField defines "json" type field for storing any serialized JSON value. // JSONField defines "json" type field for storing any serialized JSON value.
//
// The respective zero record field value is the zero [types.JSONRaw].
type JSONField struct { type JSONField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -25,6 +25,8 @@ var (
// NumberField defines "number" type field for storing numeric (float64) value. // NumberField defines "number" type field for storing numeric (float64) value.
// //
// The respective zero record field value is 0.
//
// The following additional setter keys are available: // The following additional setter keys are available:
// //
// - "fieldName+" - appends to the existing record value. For example: // - "fieldName+" - appends to the existing record value. For example:
@ -32,10 +34,22 @@ var (
// - "fieldName-" - subtracts from the existing record value. For example: // - "fieldName-" - subtracts from the existing record value. For example:
// record.Set("total-", 5) // record.Set("total-", 5)
type NumberField struct { type NumberField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -47,10 +47,22 @@ var (
// - "fieldName:hash" - returns the bcrypt hash string of the record field value (if any). For example: // - "fieldName:hash" - returns the bcrypt hash string of the record field value (if any). For example:
// record.GetString("password:hash") // record.GetString("password:hash")
type PasswordField struct { type PasswordField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -35,6 +35,8 @@ var (
// //
// If MaxSelect is > 1, then the field value is expected to be a slice of record ids. // If MaxSelect is > 1, then the field value is expected to be a slice of record ids.
// //
// The respective zero record field value is either empty string (single) or empty string slice (multiple).
//
// --- // ---
// //
// The following additional setter keys are available: // The following additional setter keys are available:
@ -51,10 +53,22 @@ var (
// //
// record.Set("categories-", "old1") // []string{"old2"} // record.Set("categories-", "old1") // []string{"old2"}
type RelationField struct { type RelationField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -35,6 +35,8 @@ var (
// //
// If MaxSelect is > 1, then the field value is expected to be a subset of Values slice. // If MaxSelect is > 1, then the field value is expected to be a subset of Values slice.
// //
// The respective zero record field value is either empty string (single) or empty string slice (multiple).
//
// --- // ---
// //
// The following additional setter keys are available: // The following additional setter keys are available:
@ -51,10 +53,22 @@ var (
// //
// record.Set("roles-", "old1") // []string{"old2"} // record.Set("roles-", "old1") // []string{"old2"}
type SelectField struct { type SelectField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -29,6 +29,8 @@ var (
// TextField defines "text" type field for storing any string value. // TextField defines "text" type field for storing any string value.
// //
// The respective zero record field value is empty string.
//
// The following additional setter keys are available: // The following additional setter keys are available:
// //
// - "fieldName:autogenerate" - autogenerate field value if AutogeneratePattern is set. For example: // - "fieldName:autogenerate" - autogenerate field value if AutogeneratePattern is set. For example:
@ -36,10 +38,22 @@ var (
// record.Set("slug:autogenerate", "") // [random value] // record.Set("slug:autogenerate", "") // [random value]
// record.Set("slug:autogenerate", "abc-") // abc-[random value] // record.Set("slug:autogenerate", "abc-") // abc-[random value]
type TextField struct { type TextField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -22,11 +22,25 @@ const FieldTypeURL = "url"
var _ Field = (*URLField)(nil) var _ Field = (*URLField)(nil)
// URLField defines "url" type field for storing URL string value. // URLField defines "url" type field for storing URL string value.
//
// The respective zero record field value is empty string.
type URLField struct { type URLField struct {
Id string `form:"id" json:"id"` // Name (required) is the unique name of the field.
Name string `form:"name" json:"name"` Name string `form:"name" json:"name"`
// Id is the unique stable field identifier.
//
// It is automatically generated from the name when adding to a collection FieldsList.
Id string `form:"id" json:"id"`
// System prevents the renaming and removal of the field.
System bool `form:"system" json:"system"` System bool `form:"system" json:"system"`
// Hidden hides the field from the API response.
Hidden bool `form:"hidden" json:"hidden"` Hidden bool `form:"hidden" json:"hidden"`
// Presentable hints the Dashboard UI to use the underlying
// field record value in the relation preview label.
Presentable bool `form:"presentable" json:"presentable"` Presentable bool `form:"presentable" json:"presentable"`
// --- // ---

View File

@ -553,7 +553,7 @@ func (app *BaseApp) FindAuthRecordByEmail(collectionModelOrIdentifier any, email
// CanAccessRecord checks if a record is allowed to be accessed by the // CanAccessRecord checks if a record is allowed to be accessed by the
// specified requestInfo and accessRule. // specified requestInfo and accessRule.
// //
// Rule and db checks are ignored in case requestInfo.AuthRecord is a superuser. // Rule and db checks are ignored in case requestInfo.Auth is a superuser.
// //
// The returned error indicate that something unexpected happened during // The returned error indicate that something unexpected happened during
// the check (eg. invalid rule or db query error). // the check (eg. invalid rule or db query error).

File diff suppressed because it is too large Load Diff

View File

@ -436,7 +436,7 @@ declare class Field implements core.Field {
interface NumberField extends core.NumberField{} // merge interface NumberField extends core.NumberField{} // merge
/** /**
* NumberField class defines a single "number" collection field. * {@inheritDoc core.NumberField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -446,7 +446,7 @@ declare class NumberField implements core.NumberField {
interface BoolField extends core.BoolField{} // merge interface BoolField extends core.BoolField{} // merge
/** /**
* BoolField class defines a single "bool" collection field. * {@inheritDoc core.BoolField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -456,7 +456,7 @@ declare class BoolField implements core.BoolField {
interface TextField extends core.TextField{} // merge interface TextField extends core.TextField{} // merge
/** /**
* TextField class defines a single "text" collection field. * {@inheritDoc core.TextField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -466,7 +466,7 @@ declare class TextField implements core.TextField {
interface URLField extends core.URLField{} // merge interface URLField extends core.URLField{} // merge
/** /**
* URLField class defines a single "url" collection field. * {@inheritDoc core.URLField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -476,7 +476,7 @@ declare class URLField implements core.URLField {
interface EmailField extends core.EmailField{} // merge interface EmailField extends core.EmailField{} // merge
/** /**
* EmailField class defines a single "email" collection field. * {@inheritDoc core.EmailField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -486,7 +486,7 @@ declare class EmailField implements core.EmailField {
interface EditorField extends core.EditorField{} // merge interface EditorField extends core.EditorField{} // merge
/** /**
* EditorField class defines a single "editor" collection field. * {@inheritDoc core.EditorField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -496,7 +496,7 @@ declare class EditorField implements core.EditorField {
interface PasswordField extends core.PasswordField{} // merge interface PasswordField extends core.PasswordField{} // merge
/** /**
* PasswordField class defines a single "password" collection field. * {@inheritDoc core.PasswordField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -506,7 +506,7 @@ declare class PasswordField implements core.PasswordField {
interface DateField extends core.DateField{} // merge interface DateField extends core.DateField{} // merge
/** /**
* DateField class defines a single "date" collection field. * {@inheritDoc core.DateField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -516,7 +516,7 @@ declare class DateField implements core.DateField {
interface AutodateField extends core.AutodateField{} // merge interface AutodateField extends core.AutodateField{} // merge
/** /**
* AutodateField class defines a single "autodate" collection field. * {@inheritDoc core.AutodateField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -526,7 +526,7 @@ declare class AutodateField implements core.AutodateField {
interface JSONField extends core.JSONField{} // merge interface JSONField extends core.JSONField{} // merge
/** /**
* JSONField class defines a single "json" collection field. * {@inheritDoc core.JSONField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -536,7 +536,7 @@ declare class JSONField implements core.JSONField {
interface RelationField extends core.RelationField{} // merge interface RelationField extends core.RelationField{} // merge
/** /**
* RelationField class defines a single "relation" collection field. * {@inheritDoc core.RelationField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -546,7 +546,7 @@ declare class RelationField implements core.RelationField {
interface SelectField extends core.SelectField{} // merge interface SelectField extends core.SelectField{} // merge
/** /**
* SelectField class defines a single "select" collection field. * {@inheritDoc core.SelectField}
* *
* @group PocketBase * @group PocketBase
*/ */
@ -556,7 +556,7 @@ declare class SelectField implements core.SelectField {
interface FileField extends core.FileField{} // merge interface FileField extends core.FileField{} // merge
/** /**
* FileField class defines a single "file" collection field. * {@inheritDoc core.FileField}
* *
* @group PocketBase * @group PocketBase
*/ */

View File

@ -60,13 +60,13 @@ type Config struct {
} }
// New creates a new PocketBase instance with the default configuration. // New creates a new PocketBase instance with the default configuration.
// Use [NewWithConfig()] if you want to provide a custom configuration. // Use [NewWithConfig] if you want to provide a custom configuration.
// //
// Note that the application will not be initialized/bootstrapped yet, // Note that the application will not be initialized/bootstrapped yet,
// aka. DB connections, migrations, app settings, etc. will not be accessible. // aka. DB connections, migrations, app settings, etc. will not be accessible.
// Everything will be initialized when [Start()] is executed. // Everything will be initialized when [PocketBase.Start()] is executed.
// If you want to initialize the application before calling [Start()], // If you want to initialize the application before calling [PocketBase.Start()],
// then you'll have to manually call [Bootstrap()]. // then you'll have to manually call [PocketBase.Bootstrap()].
func New() *PocketBase { func New() *PocketBase {
_, isUsingGoRun := inspectRuntime() _, isUsingGoRun := inspectRuntime()
@ -79,9 +79,9 @@ func New() *PocketBase {
// //
// Note that the application will not be initialized/bootstrapped yet, // Note that the application will not be initialized/bootstrapped yet,
// aka. DB connections, migrations, app settings, etc. will not be accessible. // aka. DB connections, migrations, app settings, etc. will not be accessible.
// Everything will be initialized when [Start()] is executed. // Everything will be initialized when [PocketBase.Start()] is executed.
// If you want to initialize the application before calling [Start()], // If you want to initialize the application before calling [PocketBase..Start()],
// then you'll have to manually call [Bootstrap()]. // then you'll have to manually call [PocketBase.Bootstrap()].
func NewWithConfig(config Config) *PocketBase { func NewWithConfig(config Config) *PocketBase {
// initialize a default data directory based on the executable baseDir // initialize a default data directory based on the executable baseDir
if config.DefaultDataDir == "" { if config.DefaultDataDir == "" {

View File

@ -345,12 +345,33 @@ func (e *Event) InternalServerError(message string, errData any) *ApiError {
const DefaultMaxMemory = 32 << 20 // 32mb const DefaultMaxMemory = 32 << 20 // 32mb
// Supports the following content-types: // BindBody unmarshal the request body into the provided dst.
// //
// dst must be either a struct pointer or map[string]any.
//
// The rules how the body will be scanned depends on the request Content-Type.
//
// Currently the following Content-Types are supported:
// - application/json // - application/json
// - multipart/form-data
// - application/x-www-form-urlencoded
// - text/xml, application/xml // - text/xml, application/xml
// - multipart/form-data, application/x-www-form-urlencoded
//
// Respectively the following struct tags are supported (again, which one will be used depends on the Content-Type):
// - "json" (json body)- uses the builtin Go json package for unmarshaling.
// - "xml" (xml body) - uses the builtin Go xml package for unmarshaling.
// - "form" (form data) - utilizes the custom [router.UnmarshalRequestData] method.
//
// NB! When dst is a struct make sure that it doesn't have public fields
// that shouldn't be bindable and it is advisible such fields to be unexported
// or have a separate struct just for the binding. For example:
//
// data := struct{
// somethingPrivate string
//
// Title string `json:"title" form:"title"`
// Total int `json:"total" form:"total"`
// }
// err := e.BindBody(&data)
func (e *Event) BindBody(dst any) error { func (e *Event) BindBody(dst any) error {
if e.Request.ContentLength == 0 { if e.Request.ContentLength == 0 {
return nil return nil