feat: replace config system with viper based system
allows for config settings to be easily set through ENV variables, config files, or CLI flags adds flag to run migration on web server start (fixes #29)
This commit is contained in:
parent
b28e000320
commit
c2ef8a7d56
@ -2,7 +2,11 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const TaskcafeConfDirEnvName = "TASKCAFE_CONFIG_DIR"
|
const TaskcafeConfDirEnvName = "TASKCAFE_CONFIG_DIR"
|
||||||
@ -26,6 +30,7 @@ var commandError error
|
|||||||
var configDir string
|
var configDir string
|
||||||
var verbose bool
|
var verbose bool
|
||||||
var noColor bool
|
var noColor bool
|
||||||
|
var cfgFile string
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "taskcafe",
|
Use: "taskcafe",
|
||||||
@ -33,6 +38,36 @@ var rootCmd = &cobra.Command{
|
|||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var migration http.FileSystem
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
cobra.OnInitialize(initConfig)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file path")
|
||||||
|
migration = http.Dir("./migrations")
|
||||||
|
}
|
||||||
|
|
||||||
|
func initConfig() {
|
||||||
|
if cfgFile != "" {
|
||||||
|
// Use config file from the flag.
|
||||||
|
viper.SetConfigFile(cfgFile)
|
||||||
|
} else {
|
||||||
|
// Search config in home directory with name ".cobra" (without extension).
|
||||||
|
viper.AddConfigPath("./conf")
|
||||||
|
viper.AddConfigPath(".")
|
||||||
|
viper.AddConfigPath("/etc/taskcafe")
|
||||||
|
viper.SetConfigName("taskcafe")
|
||||||
|
}
|
||||||
|
|
||||||
|
viper.SetEnvPrefix("TASKCAFE")
|
||||||
|
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
|
||||||
|
if err := viper.ReadInConfig(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
rootCmd.SetVersionTemplate(versionTemplate)
|
rootCmd.SetVersionTemplate(versionTemplate)
|
||||||
rootCmd.AddCommand(newWebCmd(), newMigrateCmd(), newTokenCmd())
|
rootCmd.AddCommand(newWebCmd(), newMigrateCmd(), newTokenCmd())
|
||||||
|
@ -2,16 +2,15 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"github.com/golang-migrate/migrate/v4"
|
"github.com/golang-migrate/migrate/v4"
|
||||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/jordanknott/taskcafe/internal/config"
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -28,33 +27,24 @@ func (l *MigrateLog) Verbose() bool {
|
|||||||
return l.verbose
|
return l.verbose
|
||||||
}
|
}
|
||||||
|
|
||||||
var migration http.FileSystem
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
migration = http.Dir("./migrations")
|
|
||||||
}
|
|
||||||
|
|
||||||
func newMigrateCmd() *cobra.Command {
|
func newMigrateCmd() *cobra.Command {
|
||||||
return &cobra.Command{
|
c := &cobra.Command{
|
||||||
Use: "migrate",
|
Use: "migrate",
|
||||||
Short: "Run the database schema migrations",
|
Short: "Run the database schema migrations",
|
||||||
Long: "Run the database schema migrations",
|
Long: "Run the database schema migrations",
|
||||||
RunE: func(cmd *cobra.Command, args []string) error {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
appConfig, err := config.LoadConfig("conf/app.toml")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
||||||
appConfig.Database.User,
|
viper.GetString("database.user"),
|
||||||
appConfig.Database.Password,
|
viper.GetString("database.password"),
|
||||||
appConfig.Database.Host,
|
viper.GetString("database.host"),
|
||||||
appConfig.Database.Name,
|
viper.GetString("database.name"),
|
||||||
)
|
)
|
||||||
db, err := sqlx.Connect("postgres", connection)
|
db, err := sqlx.Connect("postgres", connection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -71,10 +61,15 @@ func newMigrateCmd() *cobra.Command {
|
|||||||
logger := &MigrateLog{}
|
logger := &MigrateLog{}
|
||||||
m.Log = logger
|
m.Log = logger
|
||||||
err = m.Up()
|
err = m.Up()
|
||||||
if err != nil {
|
if err != nil && err != migrate.ErrNoChange {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
viper.SetDefault("database.host", "127.0.0.1")
|
||||||
|
viper.SetDefault("database.name", "taskcafe")
|
||||||
|
viper.SetDefault("database.user", "taskcafe")
|
||||||
|
viper.SetDefault("database.password", "taskcafe_test")
|
||||||
|
return c
|
||||||
}
|
}
|
||||||
|
@ -2,26 +2,28 @@ package commands
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/golang-migrate/migrate/v4"
|
||||||
|
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||||
|
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||||
|
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/jordanknott/taskcafe/internal/config"
|
|
||||||
"github.com/jordanknott/taskcafe/internal/route"
|
"github.com/jordanknott/taskcafe/internal/route"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var autoMigrate bool
|
||||||
|
|
||||||
func newWebCmd() *cobra.Command {
|
func newWebCmd() *cobra.Command {
|
||||||
return &cobra.Command{
|
cc := &cobra.Command{
|
||||||
Use: "web",
|
Use: "web",
|
||||||
Short: "Run the web server",
|
Short: "Run the web server",
|
||||||
Long: "Run the web & api server",
|
Long: "Run the web & api server",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
appConfig, err := config.LoadConfig("conf/app.toml")
|
|
||||||
if err != nil {
|
|
||||||
log.WithError(err).Error("loading config")
|
|
||||||
}
|
|
||||||
Formatter := new(log.TextFormatter)
|
Formatter := new(log.TextFormatter)
|
||||||
Formatter.TimestampFormat = "02-01-2006 15:04:05"
|
Formatter.TimestampFormat = "02-01-2006 15:04:05"
|
||||||
Formatter.FullTimestamp = true
|
Formatter.FullTimestamp = true
|
||||||
@ -29,24 +31,62 @@ func newWebCmd() *cobra.Command {
|
|||||||
log.SetLevel(log.InfoLevel)
|
log.SetLevel(log.InfoLevel)
|
||||||
|
|
||||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=disable",
|
||||||
appConfig.Database.User,
|
viper.GetString("database.user"),
|
||||||
appConfig.Database.Password,
|
viper.GetString("database.password"),
|
||||||
appConfig.Database.Host,
|
viper.GetString("database.host"),
|
||||||
appConfig.Database.Name,
|
viper.GetString("database.name"),
|
||||||
)
|
)
|
||||||
db, err := sqlx.Connect("postgres", connection)
|
db, err := sqlx.Connect("postgres", connection)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Panic(err)
|
log.Panic(err)
|
||||||
}
|
}
|
||||||
db.SetMaxOpenConns(25)
|
db.SetMaxOpenConns(25)
|
||||||
db.SetMaxIdleConns(25)
|
db.SetMaxIdleConns(25)
|
||||||
db.SetConnMaxLifetime(5 * time.Minute)
|
db.SetConnMaxLifetime(5 * time.Minute)
|
||||||
|
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
log.WithFields(log.Fields{"url": appConfig.General.Host}).Info("starting server")
|
if viper.GetBool("migrate") {
|
||||||
r, _ := route.NewRouter(appConfig, db)
|
log.Info("running auto schema migrations")
|
||||||
http.ListenAndServe(appConfig.General.Host, r)
|
if err = runMigration(db); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.WithFields(log.Fields{"url": viper.GetString("server.hostname")}).Info("starting server")
|
||||||
|
r, _ := route.NewRouter(db)
|
||||||
|
http.ListenAndServe(viper.GetString("server.hostname"), r)
|
||||||
|
return nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
cc.Flags().Bool("migrate", false, "if true, auto run's schema migrations before starting the web server")
|
||||||
|
viper.BindPFlag("migrate", cc.Flags().Lookup("migrate"))
|
||||||
|
viper.SetDefault("migrate", false)
|
||||||
|
viper.SetDefault("server.hostname", "0.0.0.0:3333")
|
||||||
|
viper.SetDefault("database.host", "127.0.0.1")
|
||||||
|
viper.SetDefault("database.name", "taskcafe")
|
||||||
|
viper.SetDefault("database.user", "taskcafe")
|
||||||
|
viper.SetDefault("database.password", "taskcafe_test")
|
||||||
|
return cc
|
||||||
|
}
|
||||||
|
|
||||||
|
func runMigration(db *sqlx.DB) error {
|
||||||
|
driver, err := postgres.WithInstance(db.DB, &postgres.Config{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := httpfs.New(migration, "./")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
m, err := migrate.NewWithInstance("httpfs", src, "postgres", driver)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logger := &MigrateLog{}
|
||||||
|
m.Log = logger
|
||||||
|
err = m.Up()
|
||||||
|
if err != nil && err != migrate.ErrNoChange {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,15 @@ import (
|
|||||||
"github.com/99designs/gqlgen/graphql/playground"
|
"github.com/99designs/gqlgen/graphql/playground"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/jordanknott/taskcafe/internal/auth"
|
"github.com/jordanknott/taskcafe/internal/auth"
|
||||||
"github.com/jordanknott/taskcafe/internal/config"
|
|
||||||
"github.com/jordanknott/taskcafe/internal/db"
|
"github.com/jordanknott/taskcafe/internal/db"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/vektah/gqlparser/v2/gqlerror"
|
"github.com/vektah/gqlparser/v2/gqlerror"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHandler returns a new graphql endpoint handler.
|
// NewHandler returns a new graphql endpoint handler.
|
||||||
func NewHandler(config config.AppConfig, repo db.Repository) http.Handler {
|
func NewHandler(repo db.Repository) http.Handler {
|
||||||
c := Config{
|
c := Config{
|
||||||
Resolvers: &Resolver{
|
Resolvers: &Resolver{
|
||||||
Config: config,
|
|
||||||
Repository: repo,
|
Repository: repo,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,10 @@ package graph
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/jordanknott/taskcafe/internal/config"
|
|
||||||
"github.com/jordanknott/taskcafe/internal/db"
|
"github.com/jordanknott/taskcafe/internal/db"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Resolver struct {
|
type Resolver struct {
|
||||||
Config config.AppConfig
|
|
||||||
Repository db.Repository
|
Repository db.Repository
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import (
|
|||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/jordanknott/taskcafe/internal/config"
|
|
||||||
"github.com/jordanknott/taskcafe/internal/db"
|
"github.com/jordanknott/taskcafe/internal/db"
|
||||||
"github.com/jordanknott/taskcafe/internal/frontend"
|
"github.com/jordanknott/taskcafe/internal/frontend"
|
||||||
"github.com/jordanknott/taskcafe/internal/graph"
|
"github.com/jordanknott/taskcafe/internal/graph"
|
||||||
@ -60,11 +59,10 @@ func (h FrontendHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type TaskcafeHandler struct {
|
type TaskcafeHandler struct {
|
||||||
config config.AppConfig
|
|
||||||
repo db.Repository
|
repo db.Repository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRouter(config config.AppConfig, dbConnection *sqlx.DB) (chi.Router, error) {
|
func NewRouter(dbConnection *sqlx.DB) (chi.Router, error) {
|
||||||
formatter := new(log.TextFormatter)
|
formatter := new(log.TextFormatter)
|
||||||
formatter.TimestampFormat = "02-01-2006 15:04:05"
|
formatter.TimestampFormat = "02-01-2006 15:04:05"
|
||||||
formatter.FullTimestamp = true
|
formatter.FullTimestamp = true
|
||||||
@ -92,7 +90,7 @@ func NewRouter(config config.AppConfig, dbConnection *sqlx.DB) (chi.Router, erro
|
|||||||
r.Use(middleware.Timeout(60 * time.Second))
|
r.Use(middleware.Timeout(60 * time.Second))
|
||||||
|
|
||||||
repository := db.NewRepository(dbConnection)
|
repository := db.NewRepository(dbConnection)
|
||||||
taskcafeHandler := TaskcafeHandler{config, *repository}
|
taskcafeHandler := TaskcafeHandler{*repository}
|
||||||
|
|
||||||
var imgServer = http.FileServer(http.Dir("./uploads/"))
|
var imgServer = http.FileServer(http.Dir("./uploads/"))
|
||||||
r.Group(func(mux chi.Router) {
|
r.Group(func(mux chi.Router) {
|
||||||
@ -105,7 +103,7 @@ func NewRouter(config config.AppConfig, dbConnection *sqlx.DB) (chi.Router, erro
|
|||||||
mux.Use(AuthenticationMiddleware)
|
mux.Use(AuthenticationMiddleware)
|
||||||
mux.Post("/users/me/avatar", taskcafeHandler.ProfileImageUpload)
|
mux.Post("/users/me/avatar", taskcafeHandler.ProfileImageUpload)
|
||||||
mux.Post("/auth/install", taskcafeHandler.InstallHandler)
|
mux.Post("/auth/install", taskcafeHandler.InstallHandler)
|
||||||
mux.Handle("/graphql", graph.NewHandler(config, *repository))
|
mux.Handle("/graphql", graph.NewHandler(*repository))
|
||||||
})
|
})
|
||||||
|
|
||||||
frontend := FrontendHandler{staticPath: "build", indexPath: "index.html"}
|
frontend := FrontendHandler{staticPath: "build", indexPath: "index.html"}
|
||||||
|
Loading…
Reference in New Issue
Block a user