Skip to content

Commit

Permalink
[pocketbase#409] added pocketbase.NewWithConfig factory
Browse files Browse the repository at this point in the history
  • Loading branch information
ganigeorgiev committed Sep 10, 2022
1 parent 4b64e09 commit 8c11e2e
Show file tree
Hide file tree
Showing 2 changed files with 151 additions and 104 deletions.
135 changes: 68 additions & 67 deletions pocketbase.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,50 +26,60 @@ type appWrapper struct {

// PocketBase defines a PocketBase app launcher.
//
// It implements [core.App] via embedding and all of interface methods
// It implements [core.App] via embedding and all of the app interface methods
// could be accessed directly through the instance (eg. PocketBase.DataDir()).
type PocketBase struct {
*appWrapper

// RootCmd is the main cli command
RootCmd *cobra.Command
debugFlag bool
dataDirFlag string
encryptionEnvFlag string
hideStartBanner bool

// console flags
debugFlag bool
dataDirFlag string
encryptionEnv string
// RootCmd is the main console command
RootCmd *cobra.Command
}

// console flag fallback values
defaultDebug bool
defaultDataDir string
defaultEncryptionEnv string
// Config is the PocketBase initialization config struct.
type Config struct {
// optional default values for the console flags
DefaultDebug bool
DefaultDataDir string // if not set, it will fallback to "./pb_data"
DefaultEncryptionEnv string

// serve start banner
showStartBanner bool
// hide the default console server info on app startup
HideStartBanner bool
}

// New creates a new PocketBase instance.
// New creates a new PocketBase instance with the default configuration.
// Use [NewWithConfig()] if you want to provide a custom configuration.
//
// Note that the application will not be initialized/bootstrapped yet,
// aka. DB connections, migrations, app settings, etc. will not be accessible.
// Everything will be initialized when Start() is executed.
// If you want to initialize the application before calling Start(),
// then you'll have to manually call Bootstrap().
// Everything will be initialized when [Start()] is executed.
// If you want to initialize the application before calling [Start()],
// then you'll have to manually call [Bootstrap()].
func New() *PocketBase {
// try to find the base executable directory and how it was run
var withGoRun bool
var baseDir string
if strings.HasPrefix(os.Args[0], os.TempDir()) {
// probably ran with go run...
withGoRun = true
baseDir, _ = os.Getwd()
} else {
// probably ran with go build...
withGoRun = false
baseDir = filepath.Dir(os.Args[0])
}
_, isUsingGoRun := inspectRuntime()

defaultDir := filepath.Join(baseDir, "pb_data")
return NewWithConfig(Config{
DefaultDebug: isUsingGoRun,
})
}

// NewWithConfig creates a new PocketBase instance with the provided config.
//
// Note that the application will not be initialized/bootstrapped yet,
// aka. DB connections, migrations, app settings, etc. will not be accessible.
// Everything will be initialized when [Start()] is executed.
// If you want to initialize the application before calling [Start()],
// then you'll have to manually call [Bootstrap()].
func NewWithConfig(config Config) *PocketBase {
// initialize a default data directory based on the executable baseDir
if config.DefaultDataDir == "" {
baseDir, _ := inspectRuntime()
config.DefaultDataDir = filepath.Join(baseDir, "pb_data")
}

pb := &PocketBase{
RootCmd: &cobra.Command{
Expand All @@ -84,19 +94,20 @@ func New() *PocketBase {
DisableDefaultCmd: true,
},
},
defaultDebug: withGoRun,
defaultDataDir: defaultDir,
defaultEncryptionEnv: "",
showStartBanner: true,
debugFlag: config.DefaultDebug,
dataDirFlag: config.DefaultDataDir,
encryptionEnvFlag: config.DefaultEncryptionEnv,
hideStartBanner: config.HideStartBanner,
}

// parse base flags
// (errors are ignored, since the full flags parsing happens on Execute())
pb.eagerParseFlags()
pb.eagerParseFlags(config)

// initialize the app instance
pb.appWrapper = &appWrapper{core.NewBaseApp(
pb.dataDirFlag,
pb.encryptionEnv,
pb.encryptionEnvFlag,
pb.debugFlag,
)}

Expand All @@ -111,35 +122,11 @@ func New() *PocketBase {
return pb
}

// DefaultDebug sets the default --debug flag value.
func (pb *PocketBase) DefaultDebug(val bool) *PocketBase {
pb.defaultDebug = val
return pb
}

// DefaultDataDir sets the default --dir flag value.
func (pb *PocketBase) DefaultDataDir(val string) *PocketBase {
pb.defaultDataDir = val
return pb
}

// DefaultEncryptionEnv sets the default --encryptionEnv flag value.
func (pb *PocketBase) DefaultEncryptionEnv(val string) *PocketBase {
pb.defaultEncryptionEnv = val
return pb
}

// ShowStartBanner shows/hides the web server start banner.
func (pb *PocketBase) ShowStartBanner(val bool) *PocketBase {
pb.showStartBanner = val
return pb
}

// Start starts the application, aka. registers the default system
// commands (serve, migrate, version) and executes pb.RootCmd.
func (pb *PocketBase) Start() error {
// register system commands
pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, pb.showStartBanner))
pb.RootCmd.AddCommand(cmd.NewServeCommand(pb, !pb.hideStartBanner))
pb.RootCmd.AddCommand(cmd.NewMigrateCommand(pb))

return pb.Execute()
Expand Down Expand Up @@ -184,27 +171,41 @@ func (pb *PocketBase) onTerminate() error {

// eagerParseFlags parses the global app flags before calling pb.RootCmd.Execute().
// so we can have all PocketBase flags ready for use on initialization.
func (pb *PocketBase) eagerParseFlags() error {
func (pb *PocketBase) eagerParseFlags(config Config) error {
pb.RootCmd.PersistentFlags().StringVar(
&pb.dataDirFlag,
"dir",
pb.defaultDataDir,
config.DefaultDataDir,
"the PocketBase data directory",
)

pb.RootCmd.PersistentFlags().StringVar(
&pb.encryptionEnv,
&pb.encryptionEnvFlag,
"encryptionEnv",
pb.defaultEncryptionEnv,
"the env variable whose value of 32 chars will be used \nas encryption key for the app settings (default none)",
config.DefaultEncryptionEnv,
"the env variable whose value of 32 characters will be used \nas encryption key for the app settings (default none)",
)

pb.RootCmd.PersistentFlags().BoolVar(
&pb.debugFlag,
"debug",
pb.defaultDebug,
config.DefaultDebug,
"enable debug mode, aka. showing more detailed logs",
)

return pb.RootCmd.ParseFlags(os.Args[1:])
}

// tries to find the base executable directory and how it was run
func inspectRuntime() (baseDir string, withGoRun bool) {
if strings.HasPrefix(os.Args[0], os.TempDir()) {
// probably ran with go run
withGoRun = true
baseDir, _ = os.Getwd()
} else {
// probably ran with go build
withGoRun = false
baseDir = filepath.Dir(os.Args[0])
}
return
}
120 changes: 83 additions & 37 deletions pocketbase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@ import (
)

func TestNew(t *testing.T) {
testDir := "./pb_test_data_dir"
defer os.RemoveAll(testDir)

// reset os.Args
// copy os.Args
originalArgs := []string{}
copy(originalArgs, os.Args)
defer func() {
// restore os.Args
copy(os.Args, originalArgs)
}()

// change os.Args
os.Args = os.Args[0:1]
os.Args = append(
os.Args,
"--dir="+testDir,
"--dir=test_dir",
"--encryptionEnv=test_encryption_env",
"--debug=true",
)
Expand All @@ -32,8 +37,8 @@ func TestNew(t *testing.T) {
t.Fatal("Expected appWrapper to be initialized, got nil")
}

if app.DataDir() != testDir {
t.Fatalf("Expected app.DataDir() %q, got %q", testDir, app.DataDir())
if app.DataDir() != "test_dir" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir", app.DataDir())
}

if app.EncryptionEnv() != "test_encryption_env" {
Expand All @@ -45,52 +50,93 @@ func TestNew(t *testing.T) {
}
}

func TestDefaultDebug(t *testing.T) {
app := New()
func TestNewWithConfig(t *testing.T) {
app := NewWithConfig(Config{
DefaultDebug: true,
DefaultDataDir: "test_dir",
DefaultEncryptionEnv: "test_encryption_env",
HideStartBanner: true,
})

app.DefaultDebug(true)
if app.defaultDebug != true {
t.Fatalf("Expected defaultDebug %v, got %v", true, app.defaultDebug)
if app == nil {
t.Fatal("Expected initialized PocketBase instance, got nil")
}

app.DefaultDebug(false)
if app.defaultDebug != false {
t.Fatalf("Expected defaultDebug %v, got %v", false, app.defaultDebug)
if app.RootCmd == nil {
t.Fatal("Expected RootCmd to be initialized, got nil")
}
}

func TestDefaultDataDir(t *testing.T) {
app := New()
if app.appWrapper == nil {
t.Fatal("Expected appWrapper to be initialized, got nil")
}

if app.hideStartBanner != true {
t.Fatal("Expected app.hideStartBanner to be true, got false")
}

if app.DataDir() != "test_dir" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir", app.DataDir())
}

expected := "test_default"
if app.EncryptionEnv() != "test_encryption_env" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env", app.EncryptionEnv())
}

app.DefaultDataDir(expected)
if app.defaultDataDir != expected {
t.Fatalf("Expected defaultDataDir %v, got %v", expected, app.defaultDataDir)
if app.IsDebug() != true {
t.Fatal("Expected app.IsDebug() true, got false")
}
}

func TestDefaultEncryptionEnv(t *testing.T) {
app := New()
func TestNewWithConfigAndFlags(t *testing.T) {
// copy os.Args
originalArgs := []string{}
copy(originalArgs, os.Args)
defer func() {
// restore os.Args
copy(os.Args, originalArgs)
}()

expected := "test_env"
// change os.Args
os.Args = os.Args[0:1]
os.Args = append(
os.Args,
"--dir=test_dir_flag",
"--encryptionEnv=test_encryption_env_flag",
"--debug=false",
)

app := NewWithConfig(Config{
DefaultDebug: true,
DefaultDataDir: "test_dir",
DefaultEncryptionEnv: "test_encryption_env",
HideStartBanner: true,
})

app.DefaultEncryptionEnv(expected)
if app.defaultEncryptionEnv != expected {
t.Fatalf("Expected defaultEncryptionEnv %v, got %v", expected, app.defaultEncryptionEnv)
if app == nil {
t.Fatal("Expected initialized PocketBase instance, got nil")
}
}

func TestShowStartBanner(t *testing.T) {
app := New()
if app.RootCmd == nil {
t.Fatal("Expected RootCmd to be initialized, got nil")
}

if app.appWrapper == nil {
t.Fatal("Expected appWrapper to be initialized, got nil")
}

if app.hideStartBanner != true {
t.Fatal("Expected app.hideStartBanner to be true, got false")
}

if app.DataDir() != "test_dir_flag" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_dir_flag", app.DataDir())
}

app.ShowStartBanner(true)
if app.showStartBanner != true {
t.Fatalf("Expected showStartBanner %v, got %v", true, app.showStartBanner)
if app.EncryptionEnv() != "test_encryption_env_flag" {
t.Fatalf("Expected app.DataDir() %q, got %q", "test_encryption_env_flag", app.EncryptionEnv())
}

app.ShowStartBanner(false)
if app.showStartBanner != false {
t.Fatalf("Expected showStartBanner %v, got %v", false, app.showStartBanner)
if app.IsDebug() != false {
t.Fatal("Expected app.IsDebug() false, got true")
}
}

0 comments on commit 8c11e2e

Please sign in to comment.