Files
htwkalender/datamanager/backend/backend.go
2024-03-03 18:21:50 +01:00

189 lines
4.2 KiB
Go

package backend
import (
"datamanager/backend/cmd"
"datamanager/backend/core"
"github.com/fatih/color"
"github.com/spf13/cobra"
"io"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
)
var _ core.App = (*Backend)(nil)
// Version of Backend
var Version = "(untracked)"
type appWrapper struct {
core.App
}
type Backend struct {
*appWrapper
devFlag bool
dataDirFlag string
encryptionEnvFlag string
hideStartBanner bool
// RootCmd is the main console command
RootCmd *cobra.Command
}
func New() *Backend {
_, isUsingGoRun := inspectRuntime()
return NewWithConfig(Config{
DefaultDev: isUsingGoRun,
DBUsername: "neo4j",
DBPassword: "htwkalender-db",
})
}
// inspectRuntime 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
}
type Config struct {
// optional default values for the console flags
DefaultDev bool
DefaultDataDir string // if not set, it will fallback to "./pb_data"
DBUsername string
DBPassword string
}
func NewWithConfig(config Config) *Backend {
// initialize a default data directory based on the executable baseDir
if config.DefaultDataDir == "" {
baseDir, _ := inspectRuntime()
config.DefaultDataDir = filepath.Join(baseDir, "data")
}
backend := &Backend{
RootCmd: &cobra.Command{
Use: filepath.Base(os.Args[0]),
Short: "Backend CLI",
Version: Version,
FParseErrWhitelist: cobra.FParseErrWhitelist{
UnknownFlags: true,
},
// no need to provide the default cobra completion command
CompletionOptions: cobra.CompletionOptions{
DisableDefaultCmd: true,
},
},
devFlag: config.DefaultDev,
dataDirFlag: config.DefaultDataDir,
}
// replace with a colored stderr writer
backend.RootCmd.SetErr(newErrWriter())
// parse base flags
// (errors are ignored, since the full flags parsing happens on Execute())
_ = backend.eagerParseFlags(&config)
// initialize the app instance
backend.appWrapper = &appWrapper{core.NewBaseApp(core.BaseAppConfig{
IsDev: backend.devFlag,
DataDir: backend.dataDirFlag,
DBUsername: config.DBUsername,
DBPassword: config.DBPassword,
})}
// hide the default help command (allow only `--help` flag)
backend.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
return backend
}
func newErrWriter() *coloredWriter {
return &coloredWriter{
w: os.Stderr,
c: color.New(color.FgRed),
}
}
// coloredWriter is a small wrapper struct to construct a [color.Color] writter.
type coloredWriter struct {
w io.Writer
c *color.Color
}
// Write writes the p bytes using the colored writer.
func (colored *coloredWriter) Write(p []byte) (n int, err error) {
colored.c.SetWriter(colored.w)
defer colored.c.UnsetWriter(colored.w)
return colored.c.Print(string(p))
}
func (backend *Backend) eagerParseFlags(config *Config) error {
backend.RootCmd.PersistentFlags().StringVar(
&backend.dataDirFlag,
"dir",
config.DefaultDataDir,
"the PocketBase data directory",
)
backend.RootCmd.PersistentFlags().BoolVar(
&backend.devFlag,
"dev",
config.DefaultDev,
"enable dev mode, aka. printing logs and cypher/sql statements to the console",
)
return backend.RootCmd.ParseFlags(os.Args[1:])
}
func (backend *Backend) Start() error {
// register system commands
backend.RootCmd.AddCommand(cmd.NewServeCommand(backend, !backend.hideStartBanner))
return backend.Execute()
}
func (backend *Backend) Execute() error {
done := make(chan bool, 1)
// listen for interrupt signal to gracefully shutdown the application
go func() {
sigch := make(chan os.Signal, 1)
signal.Notify(sigch, os.Interrupt, syscall.SIGTERM)
<-sigch
done <- true
}()
// execute the root command
go func() {
// note: leave to the commands to decide whether to print their error
_ = backend.RootCmd.Execute()
done <- true
}()
<-done
// trigger cleanups
return backend.OnTerminate().Trigger(&core.TerminateEvent{
App: backend,
}, func(e *core.TerminateEvent) error {
return e.App.ResetBootstrapState()
})
}