summaryrefslogtreecommitdiff
path: root/internal/app
diff options
context:
space:
mode:
Diffstat (limited to 'internal/app')
-rw-r--r--internal/app/action.go47
-rw-r--r--internal/app/command.go23
-rw-r--r--internal/app/flags.go127
3 files changed, 197 insertions, 0 deletions
diff --git a/internal/app/action.go b/internal/app/action.go
new file mode 100644
index 0000000..b579253
--- /dev/null
+++ b/internal/app/action.go
@@ -0,0 +1,47 @@
+package app
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+
+ "github.com/ChausseBenjamin/rafta/internal/logging"
+ "github.com/ChausseBenjamin/rafta/internal/server"
+ "github.com/ChausseBenjamin/rafta/internal/storage"
+ "github.com/urfave/cli/v3"
+)
+
+func action(ctx context.Context, cmd *cli.Command) error {
+ err := logging.Setup(
+ cmd.String(FlagLogLevel),
+ cmd.String(FlagLogFormat),
+ cmd.String(FlagLogOutput),
+ )
+ if err != nil {
+ slog.Warn("Error(s) occured during logger initialization", logging.ErrKey, err)
+ }
+
+ slog.Info("Starting rafta server")
+
+ // TODO: Setup the db
+ store, err := storage.Setup(cmd.String(FlagDBPath))
+ if err != nil {
+ slog.Error("Unable to setup database", logging.ErrKey, err)
+ }
+
+ srv, lis, err := server.Setup(cmd.Int(FlagListenPort), store)
+ if err != nil {
+ slog.Error("Unable to setup server", logging.ErrKey, err)
+
+ return err
+ }
+
+ slog.Info(fmt.Sprintf("Listening on port %d", cmd.Int(FlagListenPort)))
+ if err := srv.Serve(lis); err != nil {
+ slog.Error("Server runtime error", logging.ErrKey, err)
+
+ return err
+ }
+
+ return nil
+}
diff --git a/internal/app/command.go b/internal/app/command.go
new file mode 100644
index 0000000..5c32ba2
--- /dev/null
+++ b/internal/app/command.go
@@ -0,0 +1,23 @@
+package app
+
+import (
+ "github.com/urfave/cli/v3"
+)
+
+const (
+ AppName = "rafta"
+ AppUsage = "Really, Another Freaking Todo App?!"
+)
+
+var version = "COMPILED"
+
+func Command() *cli.Command {
+ return &cli.Command{
+ Name: AppName,
+ Usage: AppUsage,
+ Authors: []any{"Benjamin Chausse <benjamin@chausse.xyz>"},
+ Version: version,
+ Flags: flags(),
+ Action: action,
+ }
+}
diff --git a/internal/app/flags.go b/internal/app/flags.go
new file mode 100644
index 0000000..e96b8ef
--- /dev/null
+++ b/internal/app/flags.go
@@ -0,0 +1,127 @@
+package app
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "os"
+ "strings"
+
+ "github.com/ChausseBenjamin/rafta/internal/logging"
+ "github.com/ChausseBenjamin/rafta/internal/server"
+ "github.com/urfave/cli/v3"
+)
+
+const (
+ FlagListenPort = "port"
+ FlagLogLevel = "log-level"
+ FlagLogFormat = "log-format"
+ FlagLogOutput = "log-output"
+ FlagDBPath = "database"
+)
+
+func flags() []cli.Flag {
+ return []cli.Flag{
+ // Logging {{{
+ &cli.StringFlag{
+ Name: FlagLogFormat,
+ Aliases: []string{"f"},
+ Value: "plain",
+ Usage: "plain, json",
+ Sources: cli.EnvVars("LOG_FORMAT"),
+ Action: validateLogFormat,
+ },
+ &cli.StringFlag{
+ Name: FlagLogOutput,
+ Aliases: []string{"o"},
+ Value: "stdout",
+ Usage: "stdout, stderr, file",
+ Sources: cli.EnvVars("LOG_OUTPUT"),
+ Action: validateLogOutput,
+ },
+ &cli.StringFlag{
+ Name: FlagLogLevel,
+ Aliases: []string{"l"},
+ Value: "info",
+ Usage: "debug, info, warn, error",
+ Sources: cli.EnvVars("LOG_LEVEL"),
+ Action: validateLogLevel,
+ }, // }}}
+ // gRPC server {{{
+ &cli.IntFlag{
+ Name: FlagListenPort,
+ Aliases: []string{"p"},
+ Value: 1234,
+ Sources: cli.EnvVars("LISTEN_PORT"),
+ Action: validateListenPort,
+ }, // }}}
+ // Database {{{
+ &cli.StringFlag{
+ Name: FlagDBPath,
+ Aliases: []string{"d"},
+ Value: "store.db",
+ Usage: "database file",
+ Sources: cli.EnvVars("DATABASE_PATH"),
+ Action: validateDBPath,
+ }, // }}}
+ }
+}
+
+func validateLogOutput(ctx context.Context, cmd *cli.Command, s string) error {
+ switch {
+ case s == "stdout" || s == "stderr":
+ return nil
+ default:
+ // assume file
+ f, err := os.OpenFile(s, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
+ if err != nil {
+ slog.ErrorContext(
+ ctx,
+ fmt.Sprintf("Error creating/accessing provided log file %s", s),
+ )
+ return err
+ }
+ defer f.Close()
+ return nil
+ }
+}
+
+func validateLogLevel(ctx context.Context, cmd *cli.Command, s string) error {
+ for _, lvl := range []string{"deb", "inf", "warn", "err"} {
+ if strings.Contains(strings.ToLower(s), lvl) {
+ return nil
+ }
+ }
+ slog.ErrorContext(
+ ctx,
+ fmt.Sprintf("Unknown log level provided: %s", s),
+ )
+ return logging.ErrInvalidLevel
+}
+
+func validateLogFormat(ctx context.Context, cmd *cli.Command, s string) error {
+ s = strings.ToLower(s)
+ if s == "json" || s == "plain" {
+ return nil
+ }
+ return nil
+}
+
+func validateListenPort(ctx context.Context, cmd *cli.Command, p int64) error {
+ if p < 1024 || p > 65535 {
+ slog.ErrorContext(
+ ctx,
+ fmt.Sprintf("Out-of-bound port provided: %d", p),
+ )
+ return server.ErrOutOfBoundsPort
+ }
+ return nil
+}
+
+func validateDBPath(ctx context.Context, cmd *cli.Command, s string) error {
+ // TODO: Ensure the db file is writable.
+ // TODO: Ensure the db file is a valid sqlite3 db.
+ // TODO: Call db.Reset() if either of the above fail.
+ // TODO: Log the error/crash if the db file is not writable.
+ return nil
+}