initial public commit
This commit is contained in:
+424
@@ -0,0 +1,424 @@
|
||||
// Package core is the backbone of PocketBase.
|
||||
//
|
||||
// It defines the main PocketBase App interface and its base implementation.
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/pocketbase/pocketbase/tools/hook"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"github.com/pocketbase/pocketbase/tools/store"
|
||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||
)
|
||||
|
||||
// App defines the main PocketBase app interface.
|
||||
type App interface {
|
||||
// DB returns the default app database instance.
|
||||
DB() *dbx.DB
|
||||
|
||||
// Dao returns the default app Dao instance.
|
||||
//
|
||||
// This Dao could operate only on the tables and models
|
||||
// associated with the default app database. For example,
|
||||
// trying to access the request logs table will result in error.
|
||||
Dao() *daos.Dao
|
||||
|
||||
// LogsDB returns the app logs database instance.
|
||||
LogsDB() *dbx.DB
|
||||
|
||||
// LogsDao returns the app logs Dao instance.
|
||||
//
|
||||
// This Dao could operate only on the tables and models
|
||||
// associated with the logs database. For example, trying to access
|
||||
// the users table from LogsDao will result in error.
|
||||
LogsDao() *daos.Dao
|
||||
|
||||
// DataDir returns the app data directory path.
|
||||
DataDir() string
|
||||
|
||||
// EncryptionEnv returns the name of the app secret env key
|
||||
// (used for settings encryption).
|
||||
EncryptionEnv() string
|
||||
|
||||
// IsDebug returns whether the app is in debug mode
|
||||
// (showing more detailed error logs, executed sql statements, etc.).
|
||||
IsDebug() bool
|
||||
|
||||
// Settings returns the loaded app settings.
|
||||
Settings() *Settings
|
||||
|
||||
// Cache returns the app internal cache store.
|
||||
Cache() *store.Store[any]
|
||||
|
||||
// SubscriptionsBroker returns the app realtime subscriptions broker instance.
|
||||
SubscriptionsBroker() *subscriptions.Broker
|
||||
|
||||
// NewMailClient creates and returns a configured app mail client.
|
||||
NewMailClient() mailer.Mailer
|
||||
|
||||
// NewFilesystem creates and returns a configured filesystem.System instance.
|
||||
//
|
||||
// NB! Make sure to call `Close()` on the returned result
|
||||
// after you are done working with it.
|
||||
NewFilesystem() (*filesystem.System, error)
|
||||
|
||||
// RefreshSettings reinitializes and reloads the stored application settings.
|
||||
RefreshSettings() error
|
||||
|
||||
// Bootstrap takes care for initializing the application
|
||||
// (open db connections, load settings, etc.)
|
||||
Bootstrap() error
|
||||
|
||||
// ResetBootstrapState takes care for releasing initialized app resources
|
||||
// (eg. closing db connections).
|
||||
ResetBootstrapState() error
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// App event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnBeforeServe hook is triggered before serving the internal router (echo),
|
||||
// allowing you to adjust its options and attach new routes.
|
||||
OnBeforeServe() *hook.Hook[*ServeEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Dao event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// 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]
|
||||
|
||||
// OnModelAfterCreate hook is triggered after successfuly
|
||||
// inserting a new entry in the DB.
|
||||
OnModelAfterCreate() *hook.Hook[*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]
|
||||
|
||||
// OnModelAfterUpdate hook is triggered after successfuly updating
|
||||
// existing entry in the DB.
|
||||
OnModelAfterUpdate() *hook.Hook[*ModelEvent]
|
||||
|
||||
// OnModelBeforeDelete hook is triggered before deleting an
|
||||
// existing entry from the DB.
|
||||
OnModelBeforeDelete() *hook.Hook[*ModelEvent]
|
||||
|
||||
// OnModelAfterDelete is triggered after successfuly deleting an
|
||||
// existing entry from the DB.
|
||||
OnModelAfterDelete() *hook.Hook[*ModelEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Mailer event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnMailerBeforeAdminResetPasswordSend hook is triggered right before
|
||||
// sending a password reset email to an admin.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// hook.StopPropagation is returned in one of its listeners.
|
||||
OnMailerBeforeAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent]
|
||||
|
||||
// OnMailerAfterAdminResetPasswordSend hook is triggered after
|
||||
// admin password reset email was successfuly sent.
|
||||
OnMailerAfterAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent]
|
||||
|
||||
// OnMailerBeforeUserResetPasswordSend hook is triggered right before
|
||||
// sending a password reset email to a user.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// hook.StopPropagation is returned in one of its listeners.
|
||||
OnMailerBeforeUserResetPasswordSend() *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// OnMailerAfterUserResetPasswordSend hook is triggered after
|
||||
// a user password reset email was successfuly sent.
|
||||
OnMailerAfterUserResetPasswordSend() *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// OnMailerBeforeUserVerificationSend hook is triggered right before
|
||||
// sending a verification email to a user.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// hook.StopPropagation is returned in one of its listeners.
|
||||
OnMailerBeforeUserVerificationSend() *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// OnMailerAfterUserVerificationSend hook is triggered after a user
|
||||
// verification email was successfuly sent.
|
||||
OnMailerAfterUserVerificationSend() *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// OnMailerBeforeUserChangeEmailSend hook is triggered right before
|
||||
// sending a confirmation new address email to a a user.
|
||||
//
|
||||
// Could be used to send your own custom email template if
|
||||
// hook.StopPropagation is returned in one of its listeners.
|
||||
OnMailerBeforeUserChangeEmailSend() *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// OnMailerAfterUserChangeEmailSend hook is triggered after a user
|
||||
// change address email was successfuly sent.
|
||||
OnMailerAfterUserChangeEmailSend() *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Realtime API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnRealtimeConnectRequest hook is triggered right before establishing
|
||||
// the SSE client connection.
|
||||
OnRealtimeConnectRequest() *hook.Hook[*RealtimeConnectEvent]
|
||||
|
||||
// OnRealtimeBeforeSubscribeRequest hook is triggered before changing
|
||||
// the client subscriptions, allowing you to further validate and
|
||||
// modify the submitted change.
|
||||
OnRealtimeBeforeSubscribeRequest() *hook.Hook[*RealtimeSubscribeEvent]
|
||||
|
||||
// OnRealtimeAfterSubscribeRequest hook is triggered after the client
|
||||
// subscriptions were successfully changed.
|
||||
OnRealtimeAfterSubscribeRequest() *hook.Hook[*RealtimeSubscribeEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Settings API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnSettingsListRequest hook is triggered on each successfull
|
||||
// API Settings list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before
|
||||
// returning it to the client.
|
||||
OnSettingsListRequest() *hook.Hook[*SettingsListEvent]
|
||||
|
||||
// OnSettingsBeforeUpdateRequest hook is triggered before each API
|
||||
// Settings update request (after request data load and before settings persistence).
|
||||
//
|
||||
// Could be used to additionally validate the request data or
|
||||
// implement completely different persistence behavior
|
||||
// (returning hook.StopPropagation).
|
||||
OnSettingsBeforeUpdateRequest() *hook.Hook[*SettingsUpdateEvent]
|
||||
|
||||
// OnSettingsAfterUpdateRequest hook is triggered after each
|
||||
// successful API Settings update request.
|
||||
OnSettingsAfterUpdateRequest() *hook.Hook[*SettingsUpdateEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// File API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnFileDownloadRequest hook is triggered before each API File download request.
|
||||
//
|
||||
// Could be used to validate or modify the file response before
|
||||
// returning it to the client.
|
||||
OnFileDownloadRequest() *hook.Hook[*FileDownloadEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Admin API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnAdminsListRequest hook is triggered on each API Admins list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnAdminsListRequest() *hook.Hook[*AdminsListEvent]
|
||||
|
||||
// OnAdminViewRequest hook is triggered on each API Admin view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnAdminViewRequest() *hook.Hook[*AdminViewEvent]
|
||||
|
||||
// OnAdminBeforeCreateRequest hook is triggered before each API
|
||||
// Admin 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).
|
||||
OnAdminBeforeCreateRequest() *hook.Hook[*AdminCreateEvent]
|
||||
|
||||
// OnAdminAfterCreateRequest hook is triggered after each
|
||||
// successful API Admin create request.
|
||||
OnAdminAfterCreateRequest() *hook.Hook[*AdminCreateEvent]
|
||||
|
||||
// OnAdminBeforeUpdateRequest hook is triggered before each API
|
||||
// Admin 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).
|
||||
OnAdminBeforeUpdateRequest() *hook.Hook[*AdminUpdateEvent]
|
||||
|
||||
// OnAdminAfterUpdateRequest hook is triggered after each
|
||||
// successful API Admin update request.
|
||||
OnAdminAfterUpdateRequest() *hook.Hook[*AdminUpdateEvent]
|
||||
|
||||
// OnAdminBeforeDeleteRequest hook is triggered before each API
|
||||
// Admin 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).
|
||||
OnAdminBeforeDeleteRequest() *hook.Hook[*AdminDeleteEvent]
|
||||
|
||||
// OnAdminAfterDeleteRequest hook is triggered after each
|
||||
// successful API Admin delete request.
|
||||
OnAdminAfterDeleteRequest() *hook.Hook[*AdminDeleteEvent]
|
||||
|
||||
// OnAdminAuthRequest hook is triggered on each successful API Admin
|
||||
// authentication request (sign-in, token refresh, etc.).
|
||||
//
|
||||
// Could be used to additionally validate or modify the
|
||||
// authenticated admin data and token.
|
||||
OnAdminAuthRequest() *hook.Hook[*AdminAuthEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// User API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnUsersListRequest hook is triggered on each API Users list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnUsersListRequest() *hook.Hook[*UsersListEvent]
|
||||
|
||||
// OnUserViewRequest hook is triggered on each API User view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnUserViewRequest() *hook.Hook[*UserViewEvent]
|
||||
|
||||
// OnUserBeforeCreateRequest hook is triggered before each API User
|
||||
// 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).
|
||||
OnUserBeforeCreateRequest() *hook.Hook[*UserCreateEvent]
|
||||
|
||||
// OnUserAfterCreateRequest hook is triggered after each
|
||||
// successful API User create request.
|
||||
OnUserAfterCreateRequest() *hook.Hook[*UserCreateEvent]
|
||||
|
||||
// OnUserBeforeUpdateRequest hook is triggered before each API User
|
||||
// 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).
|
||||
OnUserBeforeUpdateRequest() *hook.Hook[*UserUpdateEvent]
|
||||
|
||||
// OnUserAfterUpdateRequest hook is triggered after each
|
||||
// successful API User update request.
|
||||
OnUserAfterUpdateRequest() *hook.Hook[*UserUpdateEvent]
|
||||
|
||||
// OnUserBeforeDeleteRequest hook is triggered before each API User
|
||||
// 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).
|
||||
OnUserBeforeDeleteRequest() *hook.Hook[*UserDeleteEvent]
|
||||
|
||||
// OnUserAfterDeleteRequest hook is triggered after each
|
||||
// successful API User delete request.
|
||||
OnUserAfterDeleteRequest() *hook.Hook[*UserDeleteEvent]
|
||||
|
||||
// OnUserAuthRequest hook is triggered on each successful API User
|
||||
// authentication request (sign-in, token refresh, etc.).
|
||||
//
|
||||
// Could be used to additionally validate or modify the
|
||||
// authenticated user data and token.
|
||||
OnUserAuthRequest() *hook.Hook[*UserAuthEvent]
|
||||
|
||||
// OnUserBeforeOauth2Register hook is triggered before each User OAuth2
|
||||
// authentication request (when the client config has enabled new users registration).
|
||||
//
|
||||
// Could be used to additionally validate or modify the new user
|
||||
// before persisting in the DB.
|
||||
OnUserBeforeOauth2Register() *hook.Hook[*UserOauth2RegisterEvent]
|
||||
|
||||
// OnUserAfterOauth2Register hook is triggered after each successful User
|
||||
// OAuth2 authentication sign-up request (right after the new user persistence).
|
||||
OnUserAfterOauth2Register() *hook.Hook[*UserOauth2RegisterEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Record API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// 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]
|
||||
|
||||
// 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]
|
||||
|
||||
// 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]
|
||||
|
||||
// OnRecordAfterCreateRequest hook is triggered after each
|
||||
// successful API Record create request.
|
||||
OnRecordAfterCreateRequest() *hook.Hook[*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]
|
||||
|
||||
// OnRecordAfterUpdateRequest hook is triggered after each
|
||||
// successful API Record update request.
|
||||
OnRecordAfterUpdateRequest() *hook.Hook[*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]
|
||||
|
||||
// OnRecordAfterDeleteRequest hook is triggered after each
|
||||
// successful API Record delete request.
|
||||
OnRecordAfterDeleteRequest() *hook.Hook[*RecordDeleteEvent]
|
||||
|
||||
// ---------------------------------------------------------------
|
||||
// Collection API event hooks
|
||||
// ---------------------------------------------------------------
|
||||
|
||||
// OnCollectionsListRequest hook is triggered on each API Collections list request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnCollectionsListRequest() *hook.Hook[*CollectionsListEvent]
|
||||
|
||||
// OnCollectionViewRequest hook is triggered on each API Collection view request.
|
||||
//
|
||||
// Could be used to validate or modify the response before returning it to the client.
|
||||
OnCollectionViewRequest() *hook.Hook[*CollectionViewEvent]
|
||||
|
||||
// OnCollectionBeforeCreateRequest hook is triggered before each API Collection
|
||||
// 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).
|
||||
OnCollectionBeforeCreateRequest() *hook.Hook[*CollectionCreateEvent]
|
||||
|
||||
// OnCollectionAfterCreateRequest hook is triggered after each
|
||||
// successful API Collection create request.
|
||||
OnCollectionAfterCreateRequest() *hook.Hook[*CollectionCreateEvent]
|
||||
|
||||
// OnCollectionBeforeUpdateRequest hook is triggered before each API Collection
|
||||
// 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).
|
||||
OnCollectionBeforeUpdateRequest() *hook.Hook[*CollectionUpdateEvent]
|
||||
|
||||
// OnCollectionAfterUpdateRequest hook is triggered after each
|
||||
// successful API Collection update request.
|
||||
OnCollectionAfterUpdateRequest() *hook.Hook[*CollectionUpdateEvent]
|
||||
|
||||
// OnCollectionBeforeDeleteRequest hook is triggered before each API
|
||||
// Collection 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).
|
||||
OnCollectionBeforeDeleteRequest() *hook.Hook[*CollectionDeleteEvent]
|
||||
|
||||
// OnCollectionAfterDeleteRequest hook is triggered after each
|
||||
// successful API Collection delete request.
|
||||
OnCollectionAfterDeleteRequest() *hook.Hook[*CollectionDeleteEvent]
|
||||
}
|
||||
+752
@@ -0,0 +1,752 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/pocketbase/pocketbase/tools/hook"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/pocketbase/pocketbase/tools/store"
|
||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||
)
|
||||
|
||||
var _ App = (*BaseApp)(nil)
|
||||
|
||||
// BaseApp implements core.App and defines the base PocketBase app structure.
|
||||
type BaseApp struct {
|
||||
// configurable parameters
|
||||
isDebug bool
|
||||
dataDir string
|
||||
encryptionEnv string
|
||||
|
||||
// internals
|
||||
cache *store.Store[any]
|
||||
settings *Settings
|
||||
db *dbx.DB
|
||||
dao *daos.Dao
|
||||
logsDB *dbx.DB
|
||||
logsDao *daos.Dao
|
||||
subscriptionsBroker *subscriptions.Broker
|
||||
|
||||
// serve event hooks
|
||||
onBeforeServe *hook.Hook[*ServeEvent]
|
||||
|
||||
// dao event hooks
|
||||
onModelBeforeCreate *hook.Hook[*ModelEvent]
|
||||
onModelAfterCreate *hook.Hook[*ModelEvent]
|
||||
onModelBeforeUpdate *hook.Hook[*ModelEvent]
|
||||
onModelAfterUpdate *hook.Hook[*ModelEvent]
|
||||
onModelBeforeDelete *hook.Hook[*ModelEvent]
|
||||
onModelAfterDelete *hook.Hook[*ModelEvent]
|
||||
|
||||
// mailer event hooks
|
||||
onMailerBeforeAdminResetPasswordSend *hook.Hook[*MailerAdminEvent]
|
||||
onMailerAfterAdminResetPasswordSend *hook.Hook[*MailerAdminEvent]
|
||||
onMailerBeforeUserResetPasswordSend *hook.Hook[*MailerUserEvent]
|
||||
onMailerAfterUserResetPasswordSend *hook.Hook[*MailerUserEvent]
|
||||
onMailerBeforeUserVerificationSend *hook.Hook[*MailerUserEvent]
|
||||
onMailerAfterUserVerificationSend *hook.Hook[*MailerUserEvent]
|
||||
onMailerBeforeUserChangeEmailSend *hook.Hook[*MailerUserEvent]
|
||||
onMailerAfterUserChangeEmailSend *hook.Hook[*MailerUserEvent]
|
||||
|
||||
// realtime api event hooks
|
||||
onRealtimeConnectRequest *hook.Hook[*RealtimeConnectEvent]
|
||||
onRealtimeBeforeSubscribeRequest *hook.Hook[*RealtimeSubscribeEvent]
|
||||
onRealtimeAfterSubscribeRequest *hook.Hook[*RealtimeSubscribeEvent]
|
||||
|
||||
// settings api event hooks
|
||||
onSettingsListRequest *hook.Hook[*SettingsListEvent]
|
||||
onSettingsBeforeUpdateRequest *hook.Hook[*SettingsUpdateEvent]
|
||||
onSettingsAfterUpdateRequest *hook.Hook[*SettingsUpdateEvent]
|
||||
|
||||
// file api event hooks
|
||||
onFileDownloadRequest *hook.Hook[*FileDownloadEvent]
|
||||
|
||||
// admin api event hooks
|
||||
onAdminsListRequest *hook.Hook[*AdminsListEvent]
|
||||
onAdminViewRequest *hook.Hook[*AdminViewEvent]
|
||||
onAdminBeforeCreateRequest *hook.Hook[*AdminCreateEvent]
|
||||
onAdminAfterCreateRequest *hook.Hook[*AdminCreateEvent]
|
||||
onAdminBeforeUpdateRequest *hook.Hook[*AdminUpdateEvent]
|
||||
onAdminAfterUpdateRequest *hook.Hook[*AdminUpdateEvent]
|
||||
onAdminBeforeDeleteRequest *hook.Hook[*AdminDeleteEvent]
|
||||
onAdminAfterDeleteRequest *hook.Hook[*AdminDeleteEvent]
|
||||
onAdminAuthRequest *hook.Hook[*AdminAuthEvent]
|
||||
|
||||
// user api event hooks
|
||||
onUsersListRequest *hook.Hook[*UsersListEvent]
|
||||
onUserViewRequest *hook.Hook[*UserViewEvent]
|
||||
onUserBeforeCreateRequest *hook.Hook[*UserCreateEvent]
|
||||
onUserAfterCreateRequest *hook.Hook[*UserCreateEvent]
|
||||
onUserBeforeUpdateRequest *hook.Hook[*UserUpdateEvent]
|
||||
onUserAfterUpdateRequest *hook.Hook[*UserUpdateEvent]
|
||||
onUserBeforeDeleteRequest *hook.Hook[*UserDeleteEvent]
|
||||
onUserAfterDeleteRequest *hook.Hook[*UserDeleteEvent]
|
||||
onUserAuthRequest *hook.Hook[*UserAuthEvent]
|
||||
onUserBeforeOauth2Register *hook.Hook[*UserOauth2RegisterEvent]
|
||||
onUserAfterOauth2Register *hook.Hook[*UserOauth2RegisterEvent]
|
||||
|
||||
// record api event hooks
|
||||
onRecordsListRequest *hook.Hook[*RecordsListEvent]
|
||||
onRecordViewRequest *hook.Hook[*RecordViewEvent]
|
||||
onRecordBeforeCreateRequest *hook.Hook[*RecordCreateEvent]
|
||||
onRecordAfterCreateRequest *hook.Hook[*RecordCreateEvent]
|
||||
onRecordBeforeUpdateRequest *hook.Hook[*RecordUpdateEvent]
|
||||
onRecordAfterUpdateRequest *hook.Hook[*RecordUpdateEvent]
|
||||
onRecordBeforeDeleteRequest *hook.Hook[*RecordDeleteEvent]
|
||||
onRecordAfterDeleteRequest *hook.Hook[*RecordDeleteEvent]
|
||||
|
||||
// collection api event hooks
|
||||
onCollectionsListRequest *hook.Hook[*CollectionsListEvent]
|
||||
onCollectionViewRequest *hook.Hook[*CollectionViewEvent]
|
||||
onCollectionBeforeCreateRequest *hook.Hook[*CollectionCreateEvent]
|
||||
onCollectionAfterCreateRequest *hook.Hook[*CollectionCreateEvent]
|
||||
onCollectionBeforeUpdateRequest *hook.Hook[*CollectionUpdateEvent]
|
||||
onCollectionAfterUpdateRequest *hook.Hook[*CollectionUpdateEvent]
|
||||
onCollectionBeforeDeleteRequest *hook.Hook[*CollectionDeleteEvent]
|
||||
onCollectionAfterDeleteRequest *hook.Hook[*CollectionDeleteEvent]
|
||||
}
|
||||
|
||||
// NewBaseApp creates and returns a new BaseApp instance
|
||||
// configured with the provided arguments.
|
||||
//
|
||||
// To initialize the app, you need to call `app.Bootsrap()`.
|
||||
func NewBaseApp(dataDir string, encryptionEnv string, isDebug bool) *BaseApp {
|
||||
return &BaseApp{
|
||||
dataDir: dataDir,
|
||||
isDebug: isDebug,
|
||||
encryptionEnv: encryptionEnv,
|
||||
cache: store.New[any](nil),
|
||||
settings: NewSettings(),
|
||||
subscriptionsBroker: subscriptions.NewBroker(),
|
||||
|
||||
// serve event hooks
|
||||
onBeforeServe: &hook.Hook[*ServeEvent]{},
|
||||
|
||||
// dao event hooks
|
||||
onModelBeforeCreate: &hook.Hook[*ModelEvent]{},
|
||||
onModelAfterCreate: &hook.Hook[*ModelEvent]{},
|
||||
onModelBeforeUpdate: &hook.Hook[*ModelEvent]{},
|
||||
onModelAfterUpdate: &hook.Hook[*ModelEvent]{},
|
||||
onModelBeforeDelete: &hook.Hook[*ModelEvent]{},
|
||||
onModelAfterDelete: &hook.Hook[*ModelEvent]{},
|
||||
|
||||
// mailer event hooks
|
||||
onMailerBeforeAdminResetPasswordSend: &hook.Hook[*MailerAdminEvent]{},
|
||||
onMailerAfterAdminResetPasswordSend: &hook.Hook[*MailerAdminEvent]{},
|
||||
onMailerBeforeUserResetPasswordSend: &hook.Hook[*MailerUserEvent]{},
|
||||
onMailerAfterUserResetPasswordSend: &hook.Hook[*MailerUserEvent]{},
|
||||
onMailerBeforeUserVerificationSend: &hook.Hook[*MailerUserEvent]{},
|
||||
onMailerAfterUserVerificationSend: &hook.Hook[*MailerUserEvent]{},
|
||||
onMailerBeforeUserChangeEmailSend: &hook.Hook[*MailerUserEvent]{},
|
||||
onMailerAfterUserChangeEmailSend: &hook.Hook[*MailerUserEvent]{},
|
||||
|
||||
// realtime API event hooks
|
||||
onRealtimeConnectRequest: &hook.Hook[*RealtimeConnectEvent]{},
|
||||
onRealtimeBeforeSubscribeRequest: &hook.Hook[*RealtimeSubscribeEvent]{},
|
||||
onRealtimeAfterSubscribeRequest: &hook.Hook[*RealtimeSubscribeEvent]{},
|
||||
|
||||
// settings API event hooks
|
||||
onSettingsListRequest: &hook.Hook[*SettingsListEvent]{},
|
||||
onSettingsBeforeUpdateRequest: &hook.Hook[*SettingsUpdateEvent]{},
|
||||
onSettingsAfterUpdateRequest: &hook.Hook[*SettingsUpdateEvent]{},
|
||||
|
||||
// file API event hooks
|
||||
onFileDownloadRequest: &hook.Hook[*FileDownloadEvent]{},
|
||||
|
||||
// admin API event hooks
|
||||
onAdminsListRequest: &hook.Hook[*AdminsListEvent]{},
|
||||
onAdminViewRequest: &hook.Hook[*AdminViewEvent]{},
|
||||
onAdminBeforeCreateRequest: &hook.Hook[*AdminCreateEvent]{},
|
||||
onAdminAfterCreateRequest: &hook.Hook[*AdminCreateEvent]{},
|
||||
onAdminBeforeUpdateRequest: &hook.Hook[*AdminUpdateEvent]{},
|
||||
onAdminAfterUpdateRequest: &hook.Hook[*AdminUpdateEvent]{},
|
||||
onAdminBeforeDeleteRequest: &hook.Hook[*AdminDeleteEvent]{},
|
||||
onAdminAfterDeleteRequest: &hook.Hook[*AdminDeleteEvent]{},
|
||||
onAdminAuthRequest: &hook.Hook[*AdminAuthEvent]{},
|
||||
|
||||
// user API event hooks
|
||||
onUsersListRequest: &hook.Hook[*UsersListEvent]{},
|
||||
onUserViewRequest: &hook.Hook[*UserViewEvent]{},
|
||||
onUserBeforeCreateRequest: &hook.Hook[*UserCreateEvent]{},
|
||||
onUserAfterCreateRequest: &hook.Hook[*UserCreateEvent]{},
|
||||
onUserBeforeUpdateRequest: &hook.Hook[*UserUpdateEvent]{},
|
||||
onUserAfterUpdateRequest: &hook.Hook[*UserUpdateEvent]{},
|
||||
onUserBeforeDeleteRequest: &hook.Hook[*UserDeleteEvent]{},
|
||||
onUserAfterDeleteRequest: &hook.Hook[*UserDeleteEvent]{},
|
||||
onUserAuthRequest: &hook.Hook[*UserAuthEvent]{},
|
||||
onUserBeforeOauth2Register: &hook.Hook[*UserOauth2RegisterEvent]{},
|
||||
onUserAfterOauth2Register: &hook.Hook[*UserOauth2RegisterEvent]{},
|
||||
|
||||
// record API event hooks
|
||||
onRecordsListRequest: &hook.Hook[*RecordsListEvent]{},
|
||||
onRecordViewRequest: &hook.Hook[*RecordViewEvent]{},
|
||||
onRecordBeforeCreateRequest: &hook.Hook[*RecordCreateEvent]{},
|
||||
onRecordAfterCreateRequest: &hook.Hook[*RecordCreateEvent]{},
|
||||
onRecordBeforeUpdateRequest: &hook.Hook[*RecordUpdateEvent]{},
|
||||
onRecordAfterUpdateRequest: &hook.Hook[*RecordUpdateEvent]{},
|
||||
onRecordBeforeDeleteRequest: &hook.Hook[*RecordDeleteEvent]{},
|
||||
onRecordAfterDeleteRequest: &hook.Hook[*RecordDeleteEvent]{},
|
||||
|
||||
// collection API event hooks
|
||||
onCollectionsListRequest: &hook.Hook[*CollectionsListEvent]{},
|
||||
onCollectionViewRequest: &hook.Hook[*CollectionViewEvent]{},
|
||||
onCollectionBeforeCreateRequest: &hook.Hook[*CollectionCreateEvent]{},
|
||||
onCollectionAfterCreateRequest: &hook.Hook[*CollectionCreateEvent]{},
|
||||
onCollectionBeforeUpdateRequest: &hook.Hook[*CollectionUpdateEvent]{},
|
||||
onCollectionAfterUpdateRequest: &hook.Hook[*CollectionUpdateEvent]{},
|
||||
onCollectionBeforeDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
|
||||
onCollectionAfterDeleteRequest: &hook.Hook[*CollectionDeleteEvent]{},
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap initializes the application
|
||||
// (aka. create data dir, open db connections, load settings, etc.)
|
||||
func (app *BaseApp) Bootstrap() error {
|
||||
// clear resources of previous core state (if any)
|
||||
if err := app.ResetBootstrapState(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure that data dir exist
|
||||
if err := os.MkdirAll(app.DataDir(), os.ModePerm); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := app.initDataDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := app.initLogsDB(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// we don't check for an error because the db migrations may
|
||||
// have not been executed yet.
|
||||
app.RefreshSettings()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ResetBootstrapState takes care for releasing initialized app resources
|
||||
// (eg. closing db connections).
|
||||
func (app *BaseApp) ResetBootstrapState() error {
|
||||
if app.db != nil {
|
||||
if err := app.db.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if app.logsDB != nil {
|
||||
if err := app.logsDB.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
app.dao = nil
|
||||
app.logsDao = nil
|
||||
app.settings = nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DB returns the default app database instance.
|
||||
func (app *BaseApp) DB() *dbx.DB {
|
||||
return app.db
|
||||
}
|
||||
|
||||
// Dao returns the default app Dao instance.
|
||||
func (app *BaseApp) Dao() *daos.Dao {
|
||||
return app.dao
|
||||
}
|
||||
|
||||
// LogsDB returns the app logs database instance.
|
||||
func (app *BaseApp) LogsDB() *dbx.DB {
|
||||
return app.logsDB
|
||||
}
|
||||
|
||||
// LogsDao returns the app logs Dao instance.
|
||||
func (app *BaseApp) LogsDao() *daos.Dao {
|
||||
return app.logsDao
|
||||
}
|
||||
|
||||
// DataDir returns the app data directory path.
|
||||
func (app *BaseApp) DataDir() string {
|
||||
return app.dataDir
|
||||
}
|
||||
|
||||
// EncryptionEnv returns the name of the app secret env key
|
||||
// (used for settings encryption).
|
||||
func (app *BaseApp) EncryptionEnv() string {
|
||||
return app.encryptionEnv
|
||||
}
|
||||
|
||||
// IsDebug returns whether the app is in debug mode
|
||||
// (showing more detailed error logs, executed sql statements, etc.).
|
||||
func (app *BaseApp) IsDebug() bool {
|
||||
return app.isDebug
|
||||
}
|
||||
|
||||
// Settings returns the loaded app settings.
|
||||
func (app *BaseApp) Settings() *Settings {
|
||||
return app.settings
|
||||
}
|
||||
|
||||
// Cache returns the app internal cache store.
|
||||
func (app *BaseApp) Cache() *store.Store[any] {
|
||||
return app.cache
|
||||
}
|
||||
|
||||
// SubscriptionsBroker returns the app realtime subscriptions broker instance.
|
||||
func (app *BaseApp) SubscriptionsBroker() *subscriptions.Broker {
|
||||
return app.subscriptionsBroker
|
||||
}
|
||||
|
||||
// NewMailClient creates and returns a new SMTP or Sendmail client
|
||||
// based on the current app settings.
|
||||
func (app *BaseApp) NewMailClient() mailer.Mailer {
|
||||
if app.Settings().Smtp.Enabled {
|
||||
return mailer.NewSmtpClient(
|
||||
app.Settings().Smtp.Host,
|
||||
app.Settings().Smtp.Port,
|
||||
app.Settings().Smtp.Username,
|
||||
app.Settings().Smtp.Password,
|
||||
app.Settings().Smtp.Tls,
|
||||
)
|
||||
}
|
||||
|
||||
return &mailer.Sendmail{}
|
||||
}
|
||||
|
||||
// NewFilesystem creates a new local or S3 filesystem instance
|
||||
// based on the current app settings.
|
||||
//
|
||||
// NB! Make sure to call `Close()` on the returned result
|
||||
// after you are done working with it.
|
||||
func (app *BaseApp) NewFilesystem() (*filesystem.System, error) {
|
||||
if app.settings.S3.Enabled {
|
||||
return filesystem.NewS3(
|
||||
app.settings.S3.Bucket,
|
||||
app.settings.S3.Region,
|
||||
app.settings.S3.Endpoint,
|
||||
app.settings.S3.AccessKey,
|
||||
app.settings.S3.Secret,
|
||||
)
|
||||
}
|
||||
|
||||
// fallback to local filesystem
|
||||
return filesystem.NewLocal(filepath.Join(app.DataDir(), "storage"))
|
||||
}
|
||||
|
||||
// RefreshSettings reinitializes and reloads the stored application settings.
|
||||
func (app *BaseApp) RefreshSettings() error {
|
||||
if app.settings == nil {
|
||||
app.settings = NewSettings()
|
||||
}
|
||||
|
||||
encryptionKey := os.Getenv(app.EncryptionEnv())
|
||||
|
||||
param, err := app.Dao().FindParamByKey(models.ParamAppSettings)
|
||||
if err != nil && err != sql.ErrNoRows {
|
||||
return err
|
||||
}
|
||||
|
||||
if param == nil {
|
||||
// no settings were previously stored
|
||||
return app.Dao().SaveParam(models.ParamAppSettings, app.settings, encryptionKey)
|
||||
}
|
||||
|
||||
// load the settings from the stored param into the app ones
|
||||
// ---
|
||||
newSettings := NewSettings()
|
||||
|
||||
// try first without decryption
|
||||
plainDecodeErr := json.Unmarshal(param.Value, newSettings)
|
||||
|
||||
// failed, try to decrypt
|
||||
if plainDecodeErr != nil {
|
||||
// load without decrypt has failed and there is no encryption key to use for decrypt
|
||||
if encryptionKey == "" {
|
||||
return errors.New("Failed to load the stored app settings (missing or invalid encryption key).")
|
||||
}
|
||||
|
||||
// decrypt
|
||||
decrypted, decryptErr := security.Decrypt(string(param.Value), encryptionKey)
|
||||
if decryptErr != nil {
|
||||
return decryptErr
|
||||
}
|
||||
|
||||
// decode again
|
||||
decryptedDecodeErr := json.Unmarshal(decrypted, newSettings)
|
||||
if decryptedDecodeErr != nil {
|
||||
return decryptedDecodeErr
|
||||
}
|
||||
}
|
||||
|
||||
if err := app.settings.Merge(newSettings); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if plainDecodeErr == nil && encryptionKey != "" {
|
||||
// save because previously the settings weren't stored encrypted
|
||||
saveErr := app.Dao().SaveParam(models.ParamAppSettings, app.settings, encryptionKey)
|
||||
if saveErr != nil {
|
||||
return saveErr
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Serve event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnBeforeServe() *hook.Hook[*ServeEvent] {
|
||||
return app.onBeforeServe
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Dao event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnModelBeforeCreate() *hook.Hook[*ModelEvent] {
|
||||
return app.onModelBeforeCreate
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnModelAfterCreate() *hook.Hook[*ModelEvent] {
|
||||
return app.onModelAfterCreate
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnModelBeforeUpdate() *hook.Hook[*ModelEvent] {
|
||||
return app.onModelBeforeUpdate
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnModelAfterUpdate() *hook.Hook[*ModelEvent] {
|
||||
return app.onModelAfterUpdate
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnModelBeforeDelete() *hook.Hook[*ModelEvent] {
|
||||
return app.onModelBeforeDelete
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnModelAfterDelete() *hook.Hook[*ModelEvent] {
|
||||
return app.onModelAfterDelete
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Mailer event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnMailerBeforeAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent] {
|
||||
return app.onMailerBeforeAdminResetPasswordSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerAfterAdminResetPasswordSend() *hook.Hook[*MailerAdminEvent] {
|
||||
return app.onMailerAfterAdminResetPasswordSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerBeforeUserResetPasswordSend() *hook.Hook[*MailerUserEvent] {
|
||||
return app.onMailerBeforeUserResetPasswordSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerAfterUserResetPasswordSend() *hook.Hook[*MailerUserEvent] {
|
||||
return app.onMailerAfterUserResetPasswordSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerBeforeUserVerificationSend() *hook.Hook[*MailerUserEvent] {
|
||||
return app.onMailerBeforeUserVerificationSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerAfterUserVerificationSend() *hook.Hook[*MailerUserEvent] {
|
||||
return app.onMailerAfterUserVerificationSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerBeforeUserChangeEmailSend() *hook.Hook[*MailerUserEvent] {
|
||||
return app.onMailerBeforeUserChangeEmailSend
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnMailerAfterUserChangeEmailSend() *hook.Hook[*MailerUserEvent] {
|
||||
return app.onMailerAfterUserChangeEmailSend
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Realtime API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnRealtimeConnectRequest() *hook.Hook[*RealtimeConnectEvent] {
|
||||
return app.onRealtimeConnectRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRealtimeBeforeSubscribeRequest() *hook.Hook[*RealtimeSubscribeEvent] {
|
||||
return app.onRealtimeBeforeSubscribeRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRealtimeAfterSubscribeRequest() *hook.Hook[*RealtimeSubscribeEvent] {
|
||||
return app.onRealtimeAfterSubscribeRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Settings API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnSettingsListRequest() *hook.Hook[*SettingsListEvent] {
|
||||
return app.onSettingsListRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnSettingsBeforeUpdateRequest() *hook.Hook[*SettingsUpdateEvent] {
|
||||
return app.onSettingsBeforeUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnSettingsAfterUpdateRequest() *hook.Hook[*SettingsUpdateEvent] {
|
||||
return app.onSettingsAfterUpdateRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// File API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnFileDownloadRequest() *hook.Hook[*FileDownloadEvent] {
|
||||
return app.onFileDownloadRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Admin API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnAdminsListRequest() *hook.Hook[*AdminsListEvent] {
|
||||
return app.onAdminsListRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminViewRequest() *hook.Hook[*AdminViewEvent] {
|
||||
return app.onAdminViewRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminBeforeCreateRequest() *hook.Hook[*AdminCreateEvent] {
|
||||
return app.onAdminBeforeCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminAfterCreateRequest() *hook.Hook[*AdminCreateEvent] {
|
||||
return app.onAdminAfterCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminBeforeUpdateRequest() *hook.Hook[*AdminUpdateEvent] {
|
||||
return app.onAdminBeforeUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminAfterUpdateRequest() *hook.Hook[*AdminUpdateEvent] {
|
||||
return app.onAdminAfterUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminBeforeDeleteRequest() *hook.Hook[*AdminDeleteEvent] {
|
||||
return app.onAdminBeforeDeleteRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminAfterDeleteRequest() *hook.Hook[*AdminDeleteEvent] {
|
||||
return app.onAdminAfterDeleteRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnAdminAuthRequest() *hook.Hook[*AdminAuthEvent] {
|
||||
return app.onAdminAuthRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// User API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnUsersListRequest() *hook.Hook[*UsersListEvent] {
|
||||
return app.onUsersListRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserViewRequest() *hook.Hook[*UserViewEvent] {
|
||||
return app.onUserViewRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserBeforeCreateRequest() *hook.Hook[*UserCreateEvent] {
|
||||
return app.onUserBeforeCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAfterCreateRequest() *hook.Hook[*UserCreateEvent] {
|
||||
return app.onUserAfterCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserBeforeUpdateRequest() *hook.Hook[*UserUpdateEvent] {
|
||||
return app.onUserBeforeUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAfterUpdateRequest() *hook.Hook[*UserUpdateEvent] {
|
||||
return app.onUserAfterUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserBeforeDeleteRequest() *hook.Hook[*UserDeleteEvent] {
|
||||
return app.onUserBeforeDeleteRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAfterDeleteRequest() *hook.Hook[*UserDeleteEvent] {
|
||||
return app.onUserAfterDeleteRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAuthRequest() *hook.Hook[*UserAuthEvent] {
|
||||
return app.onUserAuthRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserBeforeOauth2Register() *hook.Hook[*UserOauth2RegisterEvent] {
|
||||
return app.onUserBeforeOauth2Register
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnUserAfterOauth2Register() *hook.Hook[*UserOauth2RegisterEvent] {
|
||||
return app.onUserAfterOauth2Register
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Record API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnRecordsListRequest() *hook.Hook[*RecordsListEvent] {
|
||||
return app.onRecordsListRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordViewRequest() *hook.Hook[*RecordViewEvent] {
|
||||
return app.onRecordViewRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordBeforeCreateRequest() *hook.Hook[*RecordCreateEvent] {
|
||||
return app.onRecordBeforeCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordAfterCreateRequest() *hook.Hook[*RecordCreateEvent] {
|
||||
return app.onRecordAfterCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordBeforeUpdateRequest() *hook.Hook[*RecordUpdateEvent] {
|
||||
return app.onRecordBeforeUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordAfterUpdateRequest() *hook.Hook[*RecordUpdateEvent] {
|
||||
return app.onRecordAfterUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordBeforeDeleteRequest() *hook.Hook[*RecordDeleteEvent] {
|
||||
return app.onRecordBeforeDeleteRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnRecordAfterDeleteRequest() *hook.Hook[*RecordDeleteEvent] {
|
||||
return app.onRecordAfterDeleteRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Collection API event hooks
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) OnCollectionsListRequest() *hook.Hook[*CollectionsListEvent] {
|
||||
return app.onCollectionsListRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionViewRequest() *hook.Hook[*CollectionViewEvent] {
|
||||
return app.onCollectionViewRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionBeforeCreateRequest() *hook.Hook[*CollectionCreateEvent] {
|
||||
return app.onCollectionBeforeCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionAfterCreateRequest() *hook.Hook[*CollectionCreateEvent] {
|
||||
return app.onCollectionAfterCreateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionBeforeUpdateRequest() *hook.Hook[*CollectionUpdateEvent] {
|
||||
return app.onCollectionBeforeUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionAfterUpdateRequest() *hook.Hook[*CollectionUpdateEvent] {
|
||||
return app.onCollectionAfterUpdateRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionBeforeDeleteRequest() *hook.Hook[*CollectionDeleteEvent] {
|
||||
return app.onCollectionBeforeDeleteRequest
|
||||
}
|
||||
|
||||
func (app *BaseApp) OnCollectionAfterDeleteRequest() *hook.Hook[*CollectionDeleteEvent] {
|
||||
return app.onCollectionAfterDeleteRequest
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Helpers
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
func (app *BaseApp) initLogsDB() error {
|
||||
var connectErr error
|
||||
app.logsDB, connectErr = connectDB(filepath.Join(app.DataDir(), "logs.db"))
|
||||
if connectErr != nil {
|
||||
return connectErr
|
||||
}
|
||||
|
||||
app.logsDao = app.createDao(app.logsDB)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *BaseApp) initDataDB() error {
|
||||
var connectErr error
|
||||
app.db, connectErr = connectDB(filepath.Join(app.DataDir(), "data.db"))
|
||||
if connectErr != nil {
|
||||
return connectErr
|
||||
}
|
||||
|
||||
app.db.QueryLogFunc = func(ctx context.Context, t time.Duration, sql string, rows *sql.Rows, err error) {
|
||||
if app.IsDebug() {
|
||||
color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
|
||||
}
|
||||
}
|
||||
|
||||
app.db.ExecLogFunc = func(ctx context.Context, t time.Duration, sql string, result sql.Result, err error) {
|
||||
if app.IsDebug() {
|
||||
color.HiBlack("[%.2fms] %v\n", float64(t.Milliseconds()), sql)
|
||||
}
|
||||
}
|
||||
|
||||
app.dao = app.createDao(app.db)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (app *BaseApp) createDao(db dbx.Builder) *daos.Dao {
|
||||
dao := daos.New(db)
|
||||
|
||||
dao.BeforeCreateFunc = func(eventDao *daos.Dao, m models.Model) error {
|
||||
return app.OnModelBeforeCreate().Trigger(&ModelEvent{eventDao, m})
|
||||
}
|
||||
|
||||
dao.AfterCreateFunc = func(eventDao *daos.Dao, m models.Model) {
|
||||
app.OnModelAfterCreate().Trigger(&ModelEvent{eventDao, m})
|
||||
}
|
||||
|
||||
dao.BeforeUpdateFunc = func(eventDao *daos.Dao, m models.Model) error {
|
||||
return app.OnModelBeforeUpdate().Trigger(&ModelEvent{eventDao, m})
|
||||
}
|
||||
|
||||
dao.AfterUpdateFunc = func(eventDao *daos.Dao, m models.Model) {
|
||||
app.OnModelAfterUpdate().Trigger(&ModelEvent{eventDao, m})
|
||||
}
|
||||
|
||||
dao.BeforeDeleteFunc = func(eventDao *daos.Dao, m models.Model) error {
|
||||
return app.OnModelBeforeDelete().Trigger(&ModelEvent{eventDao, m})
|
||||
}
|
||||
|
||||
dao.AfterDeleteFunc = func(eventDao *daos.Dao, m models.Model) {
|
||||
app.OnModelAfterDelete().Trigger(&ModelEvent{eventDao, m})
|
||||
}
|
||||
|
||||
return dao
|
||||
}
|
||||
@@ -0,0 +1,438 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
)
|
||||
|
||||
func TestNewBaseApp(t *testing.T) {
|
||||
const testDataDir = "./pb_base_app_test_data_dir/"
|
||||
defer os.RemoveAll(testDataDir)
|
||||
|
||||
app := NewBaseApp(testDataDir, "test_env", true)
|
||||
|
||||
if app.dataDir != testDataDir {
|
||||
t.Fatalf("expected dataDir %q, got %q", testDataDir, app.dataDir)
|
||||
}
|
||||
|
||||
if app.encryptionEnv != "test_env" {
|
||||
t.Fatalf("expected encryptionEnv test_env, got %q", app.dataDir)
|
||||
}
|
||||
|
||||
if !app.isDebug {
|
||||
t.Fatalf("expected isDebug true, got %v", app.isDebug)
|
||||
}
|
||||
|
||||
if app.cache == nil {
|
||||
t.Fatal("expected cache to be set, got nil")
|
||||
}
|
||||
|
||||
if app.settings == nil {
|
||||
t.Fatal("expected settings to be set, got nil")
|
||||
}
|
||||
|
||||
if app.subscriptionsBroker == nil {
|
||||
t.Fatal("expected subscriptionsBroker to be set, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAppBootstrap(t *testing.T) {
|
||||
const testDataDir = "./pb_base_app_test_data_dir/"
|
||||
defer os.RemoveAll(testDataDir)
|
||||
|
||||
app := NewBaseApp(testDataDir, "pb_test_env", false)
|
||||
defer app.ResetBootstrapState()
|
||||
|
||||
// bootstrap
|
||||
if err := app.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if stat, err := os.Stat(testDataDir); err != nil || !stat.IsDir() {
|
||||
t.Fatal("Expected test data directory to be created.")
|
||||
}
|
||||
|
||||
if app.dao == nil {
|
||||
t.Fatal("Expected app.dao to be initialized, got nil.")
|
||||
}
|
||||
|
||||
if app.dao.BeforeCreateFunc == nil {
|
||||
t.Fatal("Expected app.dao.BeforeCreateFunc to be set, got nil.")
|
||||
}
|
||||
|
||||
if app.dao.AfterCreateFunc == nil {
|
||||
t.Fatal("Expected app.dao.AfterCreateFunc to be set, got nil.")
|
||||
}
|
||||
|
||||
if app.dao.BeforeUpdateFunc == nil {
|
||||
t.Fatal("Expected app.dao.BeforeUpdateFunc to be set, got nil.")
|
||||
}
|
||||
|
||||
if app.dao.AfterUpdateFunc == nil {
|
||||
t.Fatal("Expected app.dao.AfterUpdateFunc to be set, got nil.")
|
||||
}
|
||||
|
||||
if app.dao.BeforeDeleteFunc == nil {
|
||||
t.Fatal("Expected app.dao.BeforeDeleteFunc to be set, got nil.")
|
||||
}
|
||||
|
||||
if app.dao.AfterDeleteFunc == nil {
|
||||
t.Fatal("Expected app.dao.AfterDeleteFunc to be set, got nil.")
|
||||
}
|
||||
|
||||
if app.logsDao == nil {
|
||||
t.Fatal("Expected app.logsDao to be initialized, got nil.")
|
||||
}
|
||||
|
||||
if app.settings == nil {
|
||||
t.Fatal("Expected app.settings to be initialized, got nil.")
|
||||
}
|
||||
|
||||
// reset
|
||||
if err := app.ResetBootstrapState(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if app.dao != nil {
|
||||
t.Fatalf("Expected app.dao to be nil, got %v.", app.dao)
|
||||
}
|
||||
|
||||
if app.logsDao != nil {
|
||||
t.Fatalf("Expected app.logsDao to be nil, got %v.", app.logsDao)
|
||||
}
|
||||
|
||||
if app.settings != nil {
|
||||
t.Fatalf("Expected app.settings to be nil, got %v.", app.settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAppGetters(t *testing.T) {
|
||||
const testDataDir = "./pb_base_app_test_data_dir/"
|
||||
defer os.RemoveAll(testDataDir)
|
||||
|
||||
app := NewBaseApp(testDataDir, "pb_test_env", false)
|
||||
defer app.ResetBootstrapState()
|
||||
|
||||
if err := app.Bootstrap(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if app.db != app.DB() {
|
||||
t.Fatalf("Expected app.DB %v, got %v", app.DB(), app.db)
|
||||
}
|
||||
|
||||
if app.dao != app.Dao() {
|
||||
t.Fatalf("Expected app.Dao %v, got %v", app.Dao(), app.dao)
|
||||
}
|
||||
|
||||
if app.logsDB != app.LogsDB() {
|
||||
t.Fatalf("Expected app.LogsDB %v, got %v", app.LogsDB(), app.logsDB)
|
||||
}
|
||||
|
||||
if app.logsDao != app.LogsDao() {
|
||||
t.Fatalf("Expected app.LogsDao %v, got %v", app.LogsDao(), app.logsDao)
|
||||
}
|
||||
|
||||
if app.dataDir != app.DataDir() {
|
||||
t.Fatalf("Expected app.DataDir %v, got %v", app.DataDir(), app.dataDir)
|
||||
}
|
||||
|
||||
if app.encryptionEnv != app.EncryptionEnv() {
|
||||
t.Fatalf("Expected app.EncryptionEnv %v, got %v", app.EncryptionEnv(), app.encryptionEnv)
|
||||
}
|
||||
|
||||
if app.isDebug != app.IsDebug() {
|
||||
t.Fatalf("Expected app.IsDebug %v, got %v", app.IsDebug(), app.isDebug)
|
||||
}
|
||||
|
||||
if app.settings != app.Settings() {
|
||||
t.Fatalf("Expected app.Settings %v, got %v", app.Settings(), app.settings)
|
||||
}
|
||||
|
||||
if app.cache != app.Cache() {
|
||||
t.Fatalf("Expected app.Cache %v, got %v", app.Cache(), app.cache)
|
||||
}
|
||||
|
||||
if app.subscriptionsBroker != app.SubscriptionsBroker() {
|
||||
t.Fatalf("Expected app.SubscriptionsBroker %v, got %v", app.SubscriptionsBroker(), app.subscriptionsBroker)
|
||||
}
|
||||
|
||||
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.onMailerBeforeUserResetPasswordSend != app.OnMailerBeforeUserResetPasswordSend() || app.OnMailerBeforeUserResetPasswordSend() == nil {
|
||||
t.Fatalf("Getter app.OnMailerBeforeUserResetPasswordSend does not match or nil (%v vs %v)", app.OnMailerBeforeUserResetPasswordSend(), app.onMailerBeforeUserResetPasswordSend)
|
||||
}
|
||||
|
||||
if app.onMailerAfterUserResetPasswordSend != app.OnMailerAfterUserResetPasswordSend() || app.OnMailerAfterUserResetPasswordSend() == nil {
|
||||
t.Fatalf("Getter app.OnMailerAfterUserResetPasswordSend does not match or nil (%v vs %v)", app.OnMailerAfterUserResetPasswordSend(), app.onMailerAfterUserResetPasswordSend)
|
||||
}
|
||||
|
||||
if app.onMailerBeforeUserVerificationSend != app.OnMailerBeforeUserVerificationSend() || app.OnMailerBeforeUserVerificationSend() == nil {
|
||||
t.Fatalf("Getter app.OnMailerBeforeUserVerificationSend does not match or nil (%v vs %v)", app.OnMailerBeforeUserVerificationSend(), app.onMailerBeforeUserVerificationSend)
|
||||
}
|
||||
|
||||
if app.onMailerAfterUserVerificationSend != app.OnMailerAfterUserVerificationSend() || app.OnMailerAfterUserVerificationSend() == nil {
|
||||
t.Fatalf("Getter app.OnMailerAfterUserVerificationSend does not match or nil (%v vs %v)", app.OnMailerAfterUserVerificationSend(), app.onMailerAfterUserVerificationSend)
|
||||
}
|
||||
|
||||
if app.onMailerBeforeUserChangeEmailSend != app.OnMailerBeforeUserChangeEmailSend() || app.OnMailerBeforeUserChangeEmailSend() == nil {
|
||||
t.Fatalf("Getter app.OnMailerBeforeUserChangeEmailSend does not match or nil (%v vs %v)", app.OnMailerBeforeUserChangeEmailSend(), app.onMailerBeforeUserChangeEmailSend)
|
||||
}
|
||||
|
||||
if app.onMailerAfterUserChangeEmailSend != app.OnMailerAfterUserChangeEmailSend() || app.OnMailerAfterUserChangeEmailSend() == nil {
|
||||
t.Fatalf("Getter app.OnMailerAfterUserChangeEmailSend does not match or nil (%v vs %v)", app.OnMailerAfterUserChangeEmailSend(), app.onMailerAfterUserChangeEmailSend)
|
||||
}
|
||||
|
||||
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.onUsersListRequest != app.OnUsersListRequest() || app.OnUsersListRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUsersListRequest does not match or nil (%v vs %v)", app.OnUsersListRequest(), app.onUsersListRequest)
|
||||
}
|
||||
|
||||
if app.onUserViewRequest != app.OnUserViewRequest() || app.OnUserViewRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserViewRequest does not match or nil (%v vs %v)", app.OnUserViewRequest(), app.onUserViewRequest)
|
||||
}
|
||||
|
||||
if app.onUserBeforeCreateRequest != app.OnUserBeforeCreateRequest() || app.OnUserBeforeCreateRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserBeforeCreateRequest does not match or nil (%v vs %v)", app.OnUserBeforeCreateRequest(), app.onUserBeforeCreateRequest)
|
||||
}
|
||||
|
||||
if app.onUserAfterCreateRequest != app.OnUserAfterCreateRequest() || app.OnUserAfterCreateRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserAfterCreateRequest does not match or nil (%v vs %v)", app.OnUserAfterCreateRequest(), app.onUserAfterCreateRequest)
|
||||
}
|
||||
|
||||
if app.onUserBeforeUpdateRequest != app.OnUserBeforeUpdateRequest() || app.OnUserBeforeUpdateRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserBeforeUpdateRequest does not match or nil (%v vs %v)", app.OnUserBeforeUpdateRequest(), app.onUserBeforeUpdateRequest)
|
||||
}
|
||||
|
||||
if app.onUserAfterUpdateRequest != app.OnUserAfterUpdateRequest() || app.OnUserAfterUpdateRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserAfterUpdateRequest does not match or nil (%v vs %v)", app.OnUserAfterUpdateRequest(), app.onUserAfterUpdateRequest)
|
||||
}
|
||||
|
||||
if app.onUserBeforeDeleteRequest != app.OnUserBeforeDeleteRequest() || app.OnUserBeforeDeleteRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserBeforeDeleteRequest does not match or nil (%v vs %v)", app.OnUserBeforeDeleteRequest(), app.onUserBeforeDeleteRequest)
|
||||
}
|
||||
|
||||
if app.onUserAfterDeleteRequest != app.OnUserAfterDeleteRequest() || app.OnUserAfterDeleteRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserAfterDeleteRequest does not match or nil (%v vs %v)", app.OnUserAfterDeleteRequest(), app.onUserAfterDeleteRequest)
|
||||
}
|
||||
|
||||
if app.onUserAuthRequest != app.OnUserAuthRequest() || app.OnUserAuthRequest() == nil {
|
||||
t.Fatalf("Getter app.OnUserAuthRequest does not match or nil (%v vs %v)", app.OnUserAuthRequest(), app.onUserAuthRequest)
|
||||
}
|
||||
|
||||
if app.onUserBeforeOauth2Register != app.OnUserBeforeOauth2Register() || app.OnUserBeforeOauth2Register() == nil {
|
||||
t.Fatalf("Getter app.OnUserBeforeOauth2Register does not match or nil (%v vs %v)", app.OnUserBeforeOauth2Register(), app.onUserBeforeOauth2Register)
|
||||
}
|
||||
|
||||
if app.onUserAfterOauth2Register != app.OnUserAfterOauth2Register() || app.OnUserAfterOauth2Register() == nil {
|
||||
t.Fatalf("Getter app.OnUserAfterOauth2Register does not match or nil (%v vs %v)", app.OnUserAfterOauth2Register(), app.onUserAfterOauth2Register)
|
||||
}
|
||||
|
||||
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) {
|
||||
const testDataDir = "./pb_base_app_test_data_dir/"
|
||||
defer os.RemoveAll(testDataDir)
|
||||
|
||||
app := NewBaseApp(testDataDir, "pb_test_env", false)
|
||||
|
||||
client1 := app.NewMailClient()
|
||||
if val, ok := client1.(*mailer.Sendmail); !ok {
|
||||
t.Fatalf("Expected mailer.Sendmail instance, got %v", val)
|
||||
}
|
||||
|
||||
app.Settings().Smtp.Enabled = true
|
||||
|
||||
client2 := app.NewMailClient()
|
||||
if val, ok := client2.(*mailer.SmtpClient); !ok {
|
||||
t.Fatalf("Expected mailer.SmtpClient instance, got %v", val)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBaseAppNewFilesystem(t *testing.T) {
|
||||
const testDataDir = "./pb_base_app_test_data_dir/"
|
||||
defer os.RemoveAll(testDataDir)
|
||||
|
||||
app := NewBaseApp(testDataDir, "pb_test_env", false)
|
||||
|
||||
// local
|
||||
local, localErr := app.NewFilesystem()
|
||||
if localErr != nil {
|
||||
t.Fatal(localErr)
|
||||
}
|
||||
if local == nil {
|
||||
t.Fatal("Expected local filesystem instance, got nil")
|
||||
}
|
||||
|
||||
// misconfigured s3
|
||||
app.Settings().S3.Enabled = true
|
||||
s3, s3Err := app.NewFilesystem()
|
||||
if s3Err == nil {
|
||||
t.Fatal("Expected S3 error, got nil")
|
||||
}
|
||||
if s3 != nil {
|
||||
t.Fatalf("Expected nil s3 filesystem, got %v", s3)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
//go:build cgo
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func connectDB(dbPath string) (*dbx.DB, error) {
|
||||
pragmas := "_foreign_keys=1&_journal_mode=WAL&_synchronous=NORMAL&_busy_timeout=8000"
|
||||
|
||||
db, openErr := dbx.MustOpen("sqlite3", fmt.Sprintf("%s?%s", dbPath, pragmas))
|
||||
if openErr != nil {
|
||||
return nil, openErr
|
||||
}
|
||||
|
||||
// additional pragmas not supported through the dsn string
|
||||
_, err := db.NewQuery(`
|
||||
pragma journal_size_limit = 100000000;
|
||||
`).Execute()
|
||||
|
||||
return db, err
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
//go:build !cgo
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
func connectDB(dbPath string) (*dbx.DB, error) {
|
||||
pragmas := "_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)&_pragma=synchronous(NORMAL)&_pragma=busy_timeout(8000)&_pragma=journal_size_limit(100000000)"
|
||||
|
||||
return dbx.MustOpen("sqlite", fmt.Sprintf("%s?%s", dbPath, pragmas))
|
||||
}
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
"github.com/pocketbase/pocketbase/tools/auth"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"github.com/pocketbase/pocketbase/tools/search"
|
||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||
|
||||
"github.com/labstack/echo/v5"
|
||||
)
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Serve events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type ServeEvent struct {
|
||||
App App
|
||||
Router *echo.Echo
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Model DAO events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type ModelEvent struct {
|
||||
Dao *daos.Dao
|
||||
Model models.Model
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Mailer events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type MailerUserEvent struct {
|
||||
MailClient mailer.Mailer
|
||||
User *models.User
|
||||
Meta map[string]any
|
||||
}
|
||||
|
||||
type MailerAdminEvent struct {
|
||||
MailClient mailer.Mailer
|
||||
Admin *models.Admin
|
||||
Meta map[string]any
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Realtime API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type RealtimeConnectEvent struct {
|
||||
HttpContext echo.Context
|
||||
Client subscriptions.Client
|
||||
}
|
||||
|
||||
type RealtimeSubscribeEvent struct {
|
||||
HttpContext echo.Context
|
||||
Client subscriptions.Client
|
||||
Subscriptions []string
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Settings API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type SettingsListEvent struct {
|
||||
HttpContext echo.Context
|
||||
RedactedSettings *Settings
|
||||
}
|
||||
|
||||
type SettingsUpdateEvent struct {
|
||||
HttpContext echo.Context
|
||||
OldSettings *Settings
|
||||
NewSettings *Settings
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Record API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type RecordsListEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collection *models.Collection
|
||||
Records []*models.Record
|
||||
Result *search.Result
|
||||
}
|
||||
|
||||
type RecordViewEvent struct {
|
||||
HttpContext echo.Context
|
||||
Record *models.Record
|
||||
}
|
||||
|
||||
type RecordCreateEvent struct {
|
||||
HttpContext echo.Context
|
||||
Record *models.Record
|
||||
}
|
||||
|
||||
type RecordUpdateEvent struct {
|
||||
HttpContext echo.Context
|
||||
Record *models.Record
|
||||
}
|
||||
|
||||
type RecordDeleteEvent struct {
|
||||
HttpContext echo.Context
|
||||
Record *models.Record
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Admin API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type AdminsListEvent struct {
|
||||
HttpContext echo.Context
|
||||
Admins []*models.Admin
|
||||
Result *search.Result
|
||||
}
|
||||
|
||||
type AdminViewEvent struct {
|
||||
HttpContext echo.Context
|
||||
Admin *models.Admin
|
||||
}
|
||||
|
||||
type AdminCreateEvent struct {
|
||||
HttpContext echo.Context
|
||||
Admin *models.Admin
|
||||
}
|
||||
|
||||
type AdminUpdateEvent struct {
|
||||
HttpContext echo.Context
|
||||
Admin *models.Admin
|
||||
}
|
||||
|
||||
type AdminDeleteEvent struct {
|
||||
HttpContext echo.Context
|
||||
Admin *models.Admin
|
||||
}
|
||||
|
||||
type AdminAuthEvent struct {
|
||||
HttpContext echo.Context
|
||||
Admin *models.Admin
|
||||
Token string
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// User API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type UsersListEvent struct {
|
||||
HttpContext echo.Context
|
||||
Users []*models.User
|
||||
Result *search.Result
|
||||
}
|
||||
|
||||
type UserViewEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
}
|
||||
|
||||
type UserCreateEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
}
|
||||
|
||||
type UserUpdateEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
}
|
||||
|
||||
type UserDeleteEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
}
|
||||
|
||||
type UserAuthEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
Token string
|
||||
Meta any
|
||||
}
|
||||
|
||||
type UserOauth2RegisterEvent struct {
|
||||
HttpContext echo.Context
|
||||
User *models.User
|
||||
AuthData *auth.AuthUser
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// Collection API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type CollectionsListEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collections []*models.Collection
|
||||
Result *search.Result
|
||||
}
|
||||
|
||||
type CollectionViewEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collection *models.Collection
|
||||
}
|
||||
|
||||
type CollectionCreateEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collection *models.Collection
|
||||
}
|
||||
|
||||
type CollectionUpdateEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collection *models.Collection
|
||||
}
|
||||
|
||||
type CollectionDeleteEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collection *models.Collection
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// File API events data
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type FileDownloadEvent struct {
|
||||
HttpContext echo.Context
|
||||
Collection *models.Collection
|
||||
Record *models.Record
|
||||
FileField *schema.SchemaField
|
||||
ServedPath string
|
||||
ServedName string
|
||||
}
|
||||
@@ -0,0 +1,412 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/go-ozzo/ozzo-validation/v4/is"
|
||||
"github.com/pocketbase/pocketbase/tools/auth"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
)
|
||||
|
||||
// Common settings placeholder tokens
|
||||
const (
|
||||
EmailPlaceholderAppUrl string = "%APP_URL%"
|
||||
EmailPlaceholderToken string = "%TOKEN%"
|
||||
)
|
||||
|
||||
// Settings defines common app configuration options.
|
||||
type Settings struct {
|
||||
mux sync.RWMutex
|
||||
|
||||
Meta MetaConfig `form:"meta" json:"meta"`
|
||||
Logs LogsConfig `form:"logs" json:"logs"`
|
||||
Smtp SmtpConfig `form:"smtp" json:"smtp"`
|
||||
S3 S3Config `form:"s3" json:"s3"`
|
||||
AdminAuthToken TokenConfig `form:"adminAuthToken" json:"adminAuthToken"`
|
||||
AdminPasswordResetToken TokenConfig `form:"adminPasswordResetToken" json:"adminPasswordResetToken"`
|
||||
UserAuthToken TokenConfig `form:"userAuthToken" json:"userAuthToken"`
|
||||
UserPasswordResetToken TokenConfig `form:"userPasswordResetToken" json:"userPasswordResetToken"`
|
||||
UserEmailChangeToken TokenConfig `form:"userEmailChangeToken" json:"userEmailChangeToken"`
|
||||
UserVerificationToken TokenConfig `form:"userVerificationToken" json:"userVerificationToken"`
|
||||
EmailAuth EmailAuthConfig `form:"emailAuth" json:"emailAuth"`
|
||||
GoogleAuth AuthProviderConfig `form:"googleAuth" json:"googleAuth"`
|
||||
FacebookAuth AuthProviderConfig `form:"facebookAuth" json:"facebookAuth"`
|
||||
GithubAuth AuthProviderConfig `form:"githubAuth" json:"githubAuth"`
|
||||
GitlabAuth AuthProviderConfig `form:"gitlabAuth" json:"gitlabAuth"`
|
||||
}
|
||||
|
||||
// NewSettings creates and returns a new default Settings instance.
|
||||
func NewSettings() *Settings {
|
||||
return &Settings{
|
||||
Meta: MetaConfig{
|
||||
AppName: "Acme",
|
||||
AppUrl: "http://localhost:8090",
|
||||
SenderName: "Support",
|
||||
SenderAddress: "support@example.com",
|
||||
UserVerificationUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-verification/" + EmailPlaceholderToken,
|
||||
UserResetPasswordUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-password-reset/" + EmailPlaceholderToken,
|
||||
UserConfirmEmailChangeUrl: EmailPlaceholderAppUrl + "/_/#/users/confirm-email-change/" + EmailPlaceholderToken,
|
||||
},
|
||||
Logs: LogsConfig{
|
||||
MaxDays: 7,
|
||||
},
|
||||
Smtp: SmtpConfig{
|
||||
Enabled: false,
|
||||
Host: "smtp.example.com",
|
||||
Port: 587,
|
||||
Username: "",
|
||||
Password: "",
|
||||
Tls: false,
|
||||
},
|
||||
AdminAuthToken: TokenConfig{
|
||||
Secret: security.RandomString(50),
|
||||
Duration: 1209600, // 14 days,
|
||||
},
|
||||
AdminPasswordResetToken: TokenConfig{
|
||||
Secret: security.RandomString(50),
|
||||
Duration: 1800, // 30 minutes,
|
||||
},
|
||||
UserAuthToken: TokenConfig{
|
||||
Secret: security.RandomString(50),
|
||||
Duration: 1209600, // 14 days,
|
||||
},
|
||||
UserPasswordResetToken: TokenConfig{
|
||||
Secret: security.RandomString(50),
|
||||
Duration: 1800, // 30 minutes,
|
||||
},
|
||||
UserVerificationToken: TokenConfig{
|
||||
Secret: security.RandomString(50),
|
||||
Duration: 604800, // 7 days,
|
||||
},
|
||||
UserEmailChangeToken: TokenConfig{
|
||||
Secret: security.RandomString(50),
|
||||
Duration: 1800, // 30 minutes,
|
||||
},
|
||||
EmailAuth: EmailAuthConfig{
|
||||
Enabled: true,
|
||||
MinPasswordLength: 8,
|
||||
},
|
||||
GoogleAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
FacebookAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
GithubAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
GitlabAuth: AuthProviderConfig{
|
||||
Enabled: false,
|
||||
AllowRegistrations: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Validate makes Settings validatable by implementing [validation.Validatable] interface.
|
||||
func (s *Settings) Validate() error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
return validation.ValidateStruct(s,
|
||||
validation.Field(&s.Meta),
|
||||
validation.Field(&s.Logs),
|
||||
validation.Field(&s.AdminAuthToken),
|
||||
validation.Field(&s.AdminPasswordResetToken),
|
||||
validation.Field(&s.UserAuthToken),
|
||||
validation.Field(&s.UserPasswordResetToken),
|
||||
validation.Field(&s.UserEmailChangeToken),
|
||||
validation.Field(&s.UserVerificationToken),
|
||||
validation.Field(&s.Smtp),
|
||||
validation.Field(&s.S3),
|
||||
validation.Field(&s.EmailAuth),
|
||||
validation.Field(&s.GoogleAuth),
|
||||
validation.Field(&s.FacebookAuth),
|
||||
validation.Field(&s.GithubAuth),
|
||||
validation.Field(&s.GitlabAuth),
|
||||
)
|
||||
}
|
||||
|
||||
// Merge merges `other` settings into the current one.
|
||||
func (s *Settings) Merge(other *Settings) error {
|
||||
s.mux.Lock()
|
||||
defer s.mux.Unlock()
|
||||
|
||||
bytes, err := json.Marshal(other)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(bytes, s)
|
||||
}
|
||||
|
||||
// Clone creates a new deep copy of the current settings.
|
||||
func (c *Settings) Clone() (*Settings, error) {
|
||||
new := &Settings{}
|
||||
if err := new.Merge(c); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return new, nil
|
||||
}
|
||||
|
||||
// RedactClone creates a new deep copy of the current settings,
|
||||
// while replacing the secret values with `******`.
|
||||
func (s *Settings) RedactClone() (*Settings, error) {
|
||||
clone, err := s.Clone()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
mask := "******"
|
||||
|
||||
sensitiveFields := []*string{
|
||||
&clone.Smtp.Password,
|
||||
&clone.S3.Secret,
|
||||
&clone.AdminAuthToken.Secret,
|
||||
&clone.AdminPasswordResetToken.Secret,
|
||||
&clone.UserAuthToken.Secret,
|
||||
&clone.UserPasswordResetToken.Secret,
|
||||
&clone.UserEmailChangeToken.Secret,
|
||||
&clone.UserVerificationToken.Secret,
|
||||
&clone.GoogleAuth.ClientSecret,
|
||||
&clone.FacebookAuth.ClientSecret,
|
||||
&clone.GithubAuth.ClientSecret,
|
||||
&clone.GitlabAuth.ClientSecret,
|
||||
}
|
||||
|
||||
// mask all sensitive fields
|
||||
for _, v := range sensitiveFields {
|
||||
if v != nil && *v != "" {
|
||||
*v = mask
|
||||
}
|
||||
}
|
||||
|
||||
return clone, nil
|
||||
}
|
||||
|
||||
// NamedAuthProviderConfigs returns a map with all registered OAuth2
|
||||
// provider configurations (indexed by their name identifier).
|
||||
func (s *Settings) NamedAuthProviderConfigs() map[string]AuthProviderConfig {
|
||||
return map[string]AuthProviderConfig{
|
||||
auth.NameGoogle: s.GoogleAuth,
|
||||
auth.NameFacebook: s.FacebookAuth,
|
||||
auth.NameGithub: s.GithubAuth,
|
||||
auth.NameGitlab: s.GitlabAuth,
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type TokenConfig struct {
|
||||
Secret string `form:"secret" json:"secret"`
|
||||
Duration int64 `form:"duration" json:"duration"`
|
||||
}
|
||||
|
||||
// Validate makes TokenConfig validatable by implementing [validation.Validatable] interface.
|
||||
func (c TokenConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.Secret, validation.Required, validation.Length(30, 300)),
|
||||
validation.Field(&c.Duration, validation.Required, validation.Min(5), validation.Max(31536000)),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type SmtpConfig struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
Host string `form:"host" json:"host"`
|
||||
Port int `form:"port" json:"port"`
|
||||
Username string `form:"username" json:"username"`
|
||||
Password string `form:"password" json:"password"`
|
||||
|
||||
// Whether to enforce TLS encryption for the mail server connection.
|
||||
//
|
||||
// When set to false StartTLS command is send, leaving the server
|
||||
// to decide whether to upgrade the connection or not.
|
||||
Tls bool `form:"tls" json:"tls"`
|
||||
}
|
||||
|
||||
// Validate makes SmtpConfig validatable by implementing [validation.Validatable] interface.
|
||||
func (c SmtpConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.Host, is.Host, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.Port, validation.When(c.Enabled, validation.Required), validation.Min(0)),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type S3Config struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
Bucket string `form:"bucket" json:"bucket"`
|
||||
Region string `form:"region" json:"region"`
|
||||
Endpoint string `form:"endpoint" json:"endpoint"`
|
||||
AccessKey string `form:"accessKey" json:"accessKey"`
|
||||
Secret string `form:"secret" json:"secret"`
|
||||
}
|
||||
|
||||
// Validate makes S3Config validatable by implementing [validation.Validatable] interface.
|
||||
func (c S3Config) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.Endpoint, is.Host, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.Bucket, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.Region, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.AccessKey, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.Secret, validation.When(c.Enabled, validation.Required)),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type MetaConfig struct {
|
||||
AppName string `form:"appName" json:"appName"`
|
||||
AppUrl string `form:"appUrl" json:"appUrl"`
|
||||
SenderName string `form:"senderName" json:"senderName"`
|
||||
SenderAddress string `form:"senderAddress" json:"senderAddress"`
|
||||
UserVerificationUrl string `form:"userVerificationUrl" json:"userVerificationUrl"`
|
||||
UserResetPasswordUrl string `form:"userResetPasswordUrl" json:"userResetPasswordUrl"`
|
||||
UserConfirmEmailChangeUrl string `form:"userConfirmEmailChangeUrl" json:"userConfirmEmailChangeUrl"`
|
||||
}
|
||||
|
||||
// Validate makes MetaConfig validatable by implementing [validation.Validatable] interface.
|
||||
func (c MetaConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.AppName, validation.Required, validation.Length(1, 255)),
|
||||
validation.Field(&c.AppUrl, validation.Required, is.URL),
|
||||
validation.Field(&c.SenderName, validation.Required, validation.Length(1, 255)),
|
||||
validation.Field(&c.SenderAddress, is.Email, validation.Required),
|
||||
validation.Field(
|
||||
&c.UserVerificationUrl,
|
||||
validation.Required,
|
||||
validation.By(c.checkPlaceholders(EmailPlaceholderToken)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.UserResetPasswordUrl,
|
||||
validation.Required,
|
||||
validation.By(c.checkPlaceholders(EmailPlaceholderToken)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.UserConfirmEmailChangeUrl,
|
||||
validation.Required,
|
||||
validation.By(c.checkPlaceholders(EmailPlaceholderToken)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
func (c *MetaConfig) checkPlaceholders(params ...string) validation.RuleFunc {
|
||||
return func(value any) error {
|
||||
v, _ := value.(string)
|
||||
if v == "" {
|
||||
return nil // nothing to check
|
||||
}
|
||||
|
||||
for _, param := range params {
|
||||
if !strings.Contains(v, param) {
|
||||
return validation.NewError("validation_missing_required_param", fmt.Sprintf("Missing required parameter %q", param))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type LogsConfig struct {
|
||||
MaxDays int `form:"maxDays" json:"maxDays"`
|
||||
}
|
||||
|
||||
// Validate makes LogsConfig validatable by implementing [validation.Validatable] interface.
|
||||
func (c LogsConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.MaxDays, validation.Min(0)),
|
||||
)
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type AuthProviderConfig struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
AllowRegistrations bool `form:"allowRegistrations" json:"allowRegistrations"`
|
||||
ClientId string `form:"clientId" json:"clientId,omitempty"`
|
||||
ClientSecret string `form:"clientSecret" json:"clientSecret,omitempty"`
|
||||
AuthUrl string `form:"authUrl" json:"authUrl,omitempty"`
|
||||
TokenUrl string `form:"tokenUrl" json:"tokenUrl,omitempty"`
|
||||
UserApiUrl string `form:"userApiUrl" json:"userApiUrl,omitempty"`
|
||||
}
|
||||
|
||||
// Validate makes `ProviderConfig` validatable by implementing [validation.Validatable] interface.
|
||||
func (c AuthProviderConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(&c.ClientId, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.ClientSecret, validation.When(c.Enabled, validation.Required)),
|
||||
validation.Field(&c.AuthUrl, is.URL),
|
||||
validation.Field(&c.TokenUrl, is.URL),
|
||||
validation.Field(&c.UserApiUrl, is.URL),
|
||||
)
|
||||
}
|
||||
|
||||
// SetupProvider loads the current AuthProviderConfig into the specified provider.
|
||||
func (c AuthProviderConfig) SetupProvider(provider auth.Provider) error {
|
||||
if !c.Enabled {
|
||||
return errors.New("The provider is not enabled.")
|
||||
}
|
||||
|
||||
if c.ClientId != "" {
|
||||
provider.SetClientId(string(c.ClientId))
|
||||
}
|
||||
|
||||
if c.ClientSecret != "" {
|
||||
provider.SetClientSecret(string(c.ClientSecret))
|
||||
}
|
||||
|
||||
if c.AuthUrl != "" {
|
||||
provider.SetAuthUrl(c.AuthUrl)
|
||||
}
|
||||
|
||||
if c.UserApiUrl != "" {
|
||||
provider.SetUserApiUrl(c.UserApiUrl)
|
||||
}
|
||||
|
||||
if c.TokenUrl != "" {
|
||||
provider.SetTokenUrl(c.TokenUrl)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
type EmailAuthConfig struct {
|
||||
Enabled bool `form:"enabled" json:"enabled"`
|
||||
ExceptDomains []string `form:"exceptDomains" json:"exceptDomains"`
|
||||
OnlyDomains []string `form:"onlyDomains" json:"onlyDomains"`
|
||||
MinPasswordLength int `form:"minPasswordLength" json:"minPasswordLength"`
|
||||
}
|
||||
|
||||
// Validate makes `EmailAuthConfig` validatable by implementing [validation.Validatable] interface.
|
||||
func (c EmailAuthConfig) Validate() error {
|
||||
return validation.ValidateStruct(&c,
|
||||
validation.Field(
|
||||
&c.ExceptDomains,
|
||||
validation.When(len(c.OnlyDomains) > 0, validation.Empty).Else(validation.Each(is.Domain)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.OnlyDomains,
|
||||
validation.When(len(c.ExceptDomains) > 0, validation.Empty).Else(validation.Each(is.Domain)),
|
||||
),
|
||||
validation.Field(
|
||||
&c.MinPasswordLength,
|
||||
validation.When(c.Enabled, validation.Required),
|
||||
validation.Min(5),
|
||||
validation.Max(100),
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,606 @@
|
||||
package core_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/tools/auth"
|
||||
)
|
||||
|
||||
func TestSettingsValidate(t *testing.T) {
|
||||
s := core.NewSettings()
|
||||
|
||||
// set invalid settings data
|
||||
s.Meta.AppName = ""
|
||||
s.Logs.MaxDays = -10
|
||||
s.Smtp.Enabled = true
|
||||
s.Smtp.Host = ""
|
||||
s.S3.Enabled = true
|
||||
s.S3.Endpoint = "invalid"
|
||||
s.AdminAuthToken.Duration = -10
|
||||
s.AdminPasswordResetToken.Duration = -10
|
||||
s.UserAuthToken.Duration = -10
|
||||
s.UserPasswordResetToken.Duration = -10
|
||||
s.UserEmailChangeToken.Duration = -10
|
||||
s.UserVerificationToken.Duration = -10
|
||||
s.EmailAuth.Enabled = true
|
||||
s.EmailAuth.MinPasswordLength = -10
|
||||
s.GoogleAuth.Enabled = true
|
||||
s.GoogleAuth.ClientId = ""
|
||||
s.FacebookAuth.Enabled = true
|
||||
s.FacebookAuth.ClientId = ""
|
||||
s.GithubAuth.Enabled = true
|
||||
s.GithubAuth.ClientId = ""
|
||||
s.GitlabAuth.Enabled = true
|
||||
s.GitlabAuth.ClientId = ""
|
||||
|
||||
// check if Validate() is triggering the members validate methods.
|
||||
err := s.Validate()
|
||||
if err == nil {
|
||||
t.Fatalf("Expected error, got nil")
|
||||
}
|
||||
|
||||
expectations := []string{
|
||||
`"meta":{`,
|
||||
`"logs":{`,
|
||||
`"smtp":{`,
|
||||
`"s3":{`,
|
||||
`"adminAuthToken":{`,
|
||||
`"adminPasswordResetToken":{`,
|
||||
`"userAuthToken":{`,
|
||||
`"userPasswordResetToken":{`,
|
||||
`"userEmailChangeToken":{`,
|
||||
`"userVerificationToken":{`,
|
||||
`"emailAuth":{`,
|
||||
`"googleAuth":{`,
|
||||
`"facebookAuth":{`,
|
||||
`"githubAuth":{`,
|
||||
`"gitlabAuth":{`,
|
||||
}
|
||||
|
||||
errBytes, _ := json.Marshal(err)
|
||||
jsonErr := string(errBytes)
|
||||
for _, expected := range expectations {
|
||||
if !strings.Contains(jsonErr, expected) {
|
||||
t.Errorf("Expected error key %s in %v", expected, jsonErr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsMerge(t *testing.T) {
|
||||
s1 := core.NewSettings()
|
||||
s1.Meta.AppUrl = "old_app_url"
|
||||
|
||||
s2 := core.NewSettings()
|
||||
s2.Meta.AppName = "test"
|
||||
s2.Logs.MaxDays = 123
|
||||
s2.Smtp.Host = "test"
|
||||
s2.Smtp.Enabled = true
|
||||
s2.S3.Enabled = true
|
||||
s2.S3.Endpoint = "test"
|
||||
s2.AdminAuthToken.Duration = 1
|
||||
s2.AdminPasswordResetToken.Duration = 2
|
||||
s2.UserAuthToken.Duration = 3
|
||||
s2.UserPasswordResetToken.Duration = 4
|
||||
s2.UserEmailChangeToken.Duration = 5
|
||||
s2.UserVerificationToken.Duration = 6
|
||||
s2.EmailAuth.Enabled = false
|
||||
s2.EmailAuth.MinPasswordLength = 30
|
||||
s2.GoogleAuth.Enabled = true
|
||||
s2.GoogleAuth.ClientId = "google_test"
|
||||
s2.FacebookAuth.Enabled = true
|
||||
s2.FacebookAuth.ClientId = "facebook_test"
|
||||
s2.GithubAuth.Enabled = true
|
||||
s2.GithubAuth.ClientId = "github_test"
|
||||
s2.GitlabAuth.Enabled = true
|
||||
s2.GitlabAuth.ClientId = "gitlab_test"
|
||||
|
||||
if err := s1.Merge(s2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s1Encoded, err := json.Marshal(s1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2Encoded, err := json.Marshal(s2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(s1Encoded) != string(s2Encoded) {
|
||||
t.Fatalf("Expected the same serialization, got %v VS %v", string(s1Encoded), string(s2Encoded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsClone(t *testing.T) {
|
||||
s1 := core.NewSettings()
|
||||
|
||||
s2, err := s1.Clone()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s1Bytes, err := json.Marshal(s1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s2Bytes, err := json.Marshal(s2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if string(s1Bytes) != string(s2Bytes) {
|
||||
t.Fatalf("Expected equivalent serialization, got %v VS %v", string(s1Bytes), string(s2Bytes))
|
||||
}
|
||||
|
||||
// verify that it is a deep copy
|
||||
s1.Meta.AppName = "new"
|
||||
if s1.Meta.AppName == s2.Meta.AppName {
|
||||
t.Fatalf("Expected s1 and s2 to have different Meta.AppName, got %s", s1.Meta.AppName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsRedactClone(t *testing.T) {
|
||||
s1 := core.NewSettings()
|
||||
s1.Meta.AppName = "test123" // control field
|
||||
s1.Smtp.Password = "test123"
|
||||
s1.Smtp.Tls = true
|
||||
s1.S3.Secret = "test123"
|
||||
s1.AdminAuthToken.Secret = "test123"
|
||||
s1.AdminPasswordResetToken.Secret = "test123"
|
||||
s1.UserAuthToken.Secret = "test123"
|
||||
s1.UserPasswordResetToken.Secret = "test123"
|
||||
s1.UserEmailChangeToken.Secret = "test123"
|
||||
s1.UserVerificationToken.Secret = "test123"
|
||||
s1.GoogleAuth.ClientSecret = "test123"
|
||||
s1.FacebookAuth.ClientSecret = "test123"
|
||||
s1.GithubAuth.ClientSecret = "test123"
|
||||
s1.GitlabAuth.ClientSecret = "test123"
|
||||
|
||||
s2, err := s1.RedactClone()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
encoded, err := json.Marshal(s2)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `{"meta":{"appName":"test123","appUrl":"http://localhost:8090","senderName":"Support","senderAddress":"support@example.com","userVerificationUrl":"%APP_URL%/_/#/users/confirm-verification/%TOKEN%","userResetPasswordUrl":"%APP_URL%/_/#/users/confirm-password-reset/%TOKEN%","userConfirmEmailChangeUrl":"%APP_URL%/_/#/users/confirm-email-change/%TOKEN%"},"logs":{"maxDays":7},"smtp":{"enabled":false,"host":"smtp.example.com","port":587,"username":"","password":"******","tls":true},"s3":{"enabled":false,"bucket":"","region":"","endpoint":"","accessKey":"","secret":"******"},"adminAuthToken":{"secret":"******","duration":1209600},"adminPasswordResetToken":{"secret":"******","duration":1800},"userAuthToken":{"secret":"******","duration":1209600},"userPasswordResetToken":{"secret":"******","duration":1800},"userEmailChangeToken":{"secret":"******","duration":1800},"userVerificationToken":{"secret":"******","duration":604800},"emailAuth":{"enabled":true,"exceptDomains":null,"onlyDomains":null,"minPasswordLength":8},"googleAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"facebookAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"githubAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"},"gitlabAuth":{"enabled":false,"allowRegistrations":true,"clientSecret":"******"}}`
|
||||
|
||||
if encodedStr := string(encoded); encodedStr != expected {
|
||||
t.Fatalf("Expected %v, got \n%v", expected, encodedStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNamedAuthProviderConfigs(t *testing.T) {
|
||||
s := core.NewSettings()
|
||||
|
||||
s.GoogleAuth.ClientId = "google_test"
|
||||
s.FacebookAuth.ClientId = "facebook_test"
|
||||
s.GithubAuth.ClientId = "github_test"
|
||||
s.GitlabAuth.ClientId = "gitlab_test"
|
||||
s.GitlabAuth.Enabled = true
|
||||
|
||||
result := s.NamedAuthProviderConfigs()
|
||||
|
||||
encoded, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expected := `{"facebook":{"enabled":false,"allowRegistrations":true,"clientId":"facebook_test"},"github":{"enabled":false,"allowRegistrations":true,"clientId":"github_test"},"gitlab":{"enabled":true,"allowRegistrations":true,"clientId":"gitlab_test"},"google":{"enabled":false,"allowRegistrations":true,"clientId":"google_test"}}`
|
||||
|
||||
if encodedStr := string(encoded); encodedStr != expected {
|
||||
t.Fatalf("Expected the same serialization, got %v", encodedStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.TokenConfig
|
||||
expectError bool
|
||||
}{
|
||||
// zero values
|
||||
{
|
||||
core.TokenConfig{},
|
||||
true,
|
||||
},
|
||||
// invalid data
|
||||
{
|
||||
core.TokenConfig{
|
||||
Secret: "test",
|
||||
Duration: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.TokenConfig{
|
||||
Secret: "testtesttesttesttesttesttestte",
|
||||
Duration: 100,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSmtpConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.SmtpConfig
|
||||
expectError bool
|
||||
}{
|
||||
// zero values (disabled)
|
||||
{
|
||||
core.SmtpConfig{},
|
||||
false,
|
||||
},
|
||||
// zero values (enabled)
|
||||
{
|
||||
core.SmtpConfig{Enabled: true},
|
||||
true,
|
||||
},
|
||||
// invalid data
|
||||
{
|
||||
core.SmtpConfig{
|
||||
Enabled: true,
|
||||
Host: "test:test:test",
|
||||
Port: -10,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.SmtpConfig{
|
||||
Enabled: true,
|
||||
Host: "example.com",
|
||||
Port: 100,
|
||||
Tls: true,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestS3ConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.S3Config
|
||||
expectError bool
|
||||
}{
|
||||
// zero values (disabled)
|
||||
{
|
||||
core.S3Config{},
|
||||
false,
|
||||
},
|
||||
// zero values (enabled)
|
||||
{
|
||||
core.S3Config{Enabled: true},
|
||||
true,
|
||||
},
|
||||
// invalid data
|
||||
{
|
||||
core.S3Config{
|
||||
Enabled: true,
|
||||
Endpoint: "test:test:test",
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.S3Config{
|
||||
Enabled: true,
|
||||
Endpoint: "example.com",
|
||||
Bucket: "test",
|
||||
Region: "test",
|
||||
AccessKey: "test",
|
||||
Secret: "test",
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMetaConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.MetaConfig
|
||||
expectError bool
|
||||
}{
|
||||
// zero values
|
||||
{
|
||||
core.MetaConfig{},
|
||||
true,
|
||||
},
|
||||
// invalid data
|
||||
{
|
||||
core.MetaConfig{
|
||||
AppName: strings.Repeat("a", 300),
|
||||
AppUrl: "test",
|
||||
SenderName: strings.Repeat("a", 300),
|
||||
SenderAddress: "invalid_email",
|
||||
UserVerificationUrl: "test",
|
||||
UserResetPasswordUrl: "test",
|
||||
UserConfirmEmailChangeUrl: "test",
|
||||
},
|
||||
true,
|
||||
},
|
||||
// invalid data (missing required placeholders)
|
||||
{
|
||||
core.MetaConfig{
|
||||
AppName: "test",
|
||||
AppUrl: "https://example.com",
|
||||
SenderName: "test",
|
||||
SenderAddress: "test@example.com",
|
||||
UserVerificationUrl: "https://example.com",
|
||||
UserResetPasswordUrl: "https://example.com",
|
||||
UserConfirmEmailChangeUrl: "https://example.com",
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.MetaConfig{
|
||||
AppName: "test",
|
||||
AppUrl: "https://example.com",
|
||||
SenderName: "test",
|
||||
SenderAddress: "test@example.com",
|
||||
UserVerificationUrl: "https://example.com/" + core.EmailPlaceholderToken,
|
||||
UserResetPasswordUrl: "https://example.com/" + core.EmailPlaceholderToken,
|
||||
UserConfirmEmailChangeUrl: "https://example.com/" + core.EmailPlaceholderToken,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogsConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.LogsConfig
|
||||
expectError bool
|
||||
}{
|
||||
// zero values
|
||||
{
|
||||
core.LogsConfig{},
|
||||
false,
|
||||
},
|
||||
// invalid data
|
||||
{
|
||||
core.LogsConfig{MaxDays: -10},
|
||||
true,
|
||||
},
|
||||
// valid data
|
||||
{
|
||||
core.LogsConfig{MaxDays: 1},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthProviderConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.AuthProviderConfig
|
||||
expectError bool
|
||||
}{
|
||||
// zero values (disabled)
|
||||
{
|
||||
core.AuthProviderConfig{},
|
||||
false,
|
||||
},
|
||||
// zero values (enabled)
|
||||
{
|
||||
core.AuthProviderConfig{Enabled: true},
|
||||
true,
|
||||
},
|
||||
// invalid data
|
||||
{
|
||||
core.AuthProviderConfig{
|
||||
Enabled: true,
|
||||
ClientId: "",
|
||||
ClientSecret: "",
|
||||
AuthUrl: "test",
|
||||
TokenUrl: "test",
|
||||
UserApiUrl: "test",
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data (only the required)
|
||||
{
|
||||
core.AuthProviderConfig{
|
||||
Enabled: true,
|
||||
ClientId: "test",
|
||||
ClientSecret: "test",
|
||||
},
|
||||
false,
|
||||
},
|
||||
// valid data (fill all fields)
|
||||
{
|
||||
core.AuthProviderConfig{
|
||||
Enabled: true,
|
||||
ClientId: "test",
|
||||
ClientSecret: "test",
|
||||
AuthUrl: "https://example.com",
|
||||
TokenUrl: "https://example.com",
|
||||
UserApiUrl: "https://example.com",
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthProviderConfigSetupProvider(t *testing.T) {
|
||||
provider := auth.NewGithubProvider()
|
||||
|
||||
// disabled config
|
||||
c1 := core.AuthProviderConfig{Enabled: false}
|
||||
if err := c1.SetupProvider(provider); err == nil {
|
||||
t.Errorf("Expected error, got nil")
|
||||
}
|
||||
|
||||
c2 := core.AuthProviderConfig{
|
||||
Enabled: true,
|
||||
ClientId: "test_ClientId",
|
||||
ClientSecret: "test_ClientSecret",
|
||||
AuthUrl: "test_AuthUrl",
|
||||
UserApiUrl: "test_UserApiUrl",
|
||||
TokenUrl: "test_TokenUrl",
|
||||
}
|
||||
if err := c2.SetupProvider(provider); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
encoded, _ := json.Marshal(c2)
|
||||
expected := `{"enabled":true,"allowRegistrations":false,"clientId":"test_ClientId","clientSecret":"test_ClientSecret","authUrl":"test_AuthUrl","tokenUrl":"test_TokenUrl","userApiUrl":"test_UserApiUrl"}`
|
||||
if string(encoded) != expected {
|
||||
t.Errorf("Expected %s, got %s", expected, string(encoded))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEmailAuthConfigValidate(t *testing.T) {
|
||||
scenarios := []struct {
|
||||
config core.EmailAuthConfig
|
||||
expectError bool
|
||||
}{
|
||||
// zero values (disabled)
|
||||
{
|
||||
core.EmailAuthConfig{},
|
||||
false,
|
||||
},
|
||||
// zero values (enabled)
|
||||
{
|
||||
core.EmailAuthConfig{Enabled: true},
|
||||
true,
|
||||
},
|
||||
// invalid data (only the required)
|
||||
{
|
||||
core.EmailAuthConfig{
|
||||
Enabled: true,
|
||||
MinPasswordLength: 4,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data (only the required)
|
||||
{
|
||||
core.EmailAuthConfig{
|
||||
Enabled: true,
|
||||
MinPasswordLength: 5,
|
||||
},
|
||||
false,
|
||||
},
|
||||
// invalid data (both OnlyDomains and ExceptDomains set)
|
||||
{
|
||||
core.EmailAuthConfig{
|
||||
Enabled: true,
|
||||
MinPasswordLength: 5,
|
||||
OnlyDomains: []string{"example.com", "test.com"},
|
||||
ExceptDomains: []string{"example.com", "test.com"},
|
||||
},
|
||||
true,
|
||||
},
|
||||
// valid data (only onlyDomains set)
|
||||
{
|
||||
core.EmailAuthConfig{
|
||||
Enabled: true,
|
||||
MinPasswordLength: 5,
|
||||
OnlyDomains: []string{"example.com", "test.com"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
// valid data (only exceptDomains set)
|
||||
{
|
||||
core.EmailAuthConfig{
|
||||
Enabled: true,
|
||||
MinPasswordLength: 5,
|
||||
ExceptDomains: []string{"example.com", "test.com"},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for i, scenario := range scenarios {
|
||||
result := scenario.config.Validate()
|
||||
|
||||
if result != nil && !scenario.expectError {
|
||||
t.Errorf("(%d) Didn't expect error, got %v", i, result)
|
||||
}
|
||||
|
||||
if result == nil && scenario.expectError {
|
||||
t.Errorf("(%d) Expected error, got nil", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user