refactor: move config related code into dedicated package
This commit is contained in:
parent
54553cfbdd
commit
800dd2014c
@ -5,6 +5,7 @@ import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/utils"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
@ -52,6 +53,7 @@ func initConfig() {
|
||||
viper.SetEnvPrefix("TASKCAFE")
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
viper.AutomaticEnv()
|
||||
config.InitDefaults()
|
||||
|
||||
err := viper.ReadInConfig()
|
||||
if err == nil {
|
||||
@ -61,30 +63,10 @@ func initConfig() {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
viper.SetDefault("queue.broker", "amqp://guest:guest@localhost:5672/")
|
||||
viper.SetDefault("queue.store", "memcache://localhost:11211")
|
||||
|
||||
}
|
||||
|
||||
// Execute the root cobra command
|
||||
func Execute() {
|
||||
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")
|
||||
viper.SetDefault("database.port", "5432")
|
||||
viper.SetDefault("security.token_expiration", "15m")
|
||||
|
||||
viper.SetDefault("queue.broker", "amqp://guest:guest@localhost:5672/")
|
||||
viper.SetDefault("queue.store", "memcache://localhost:11211")
|
||||
|
||||
rootCmd.SetVersionTemplate(VersionTemplate())
|
||||
rootCmd.AddCommand(newWebCmd(), newMigrateCmd(), newWorkerCmd(), newResetPasswordCmd(), newSeedCmd())
|
||||
rootCmd.Execute()
|
||||
|
@ -1,21 +1,18 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
||||
"github.com/google/uuid"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/route"
|
||||
"github.com/jordanknott/taskcafe/internal/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@ -33,13 +30,7 @@ func newWebCmd() *cobra.Command {
|
||||
log.SetFormatter(Formatter)
|
||||
log.SetLevel(log.InfoLevel)
|
||||
|
||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s port=%s sslmode=disable",
|
||||
viper.GetString("database.user"),
|
||||
viper.GetString("database.password"),
|
||||
viper.GetString("database.host"),
|
||||
viper.GetString("database.name"),
|
||||
viper.GetString("database.port"),
|
||||
)
|
||||
connection := config.GetDatabaseConnectionUri()
|
||||
var db *sqlx.DB
|
||||
var err error
|
||||
var retryDuration time.Duration
|
||||
@ -70,36 +61,19 @@ func newWebCmd() *cobra.Command {
|
||||
}
|
||||
}
|
||||
|
||||
secret := viper.GetString("server.secret")
|
||||
if strings.TrimSpace(secret) == "" {
|
||||
log.Warn("server.secret is not set, generating a random secret")
|
||||
secret = uuid.New().String()
|
||||
appConfig, err := config.GetAppConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
security, err := utils.GetSecurityConfig(viper.GetString("security.token_expiration"), []byte(secret))
|
||||
r, _ := route.NewRouter(db, utils.EmailConfig{
|
||||
From: viper.GetString("smtp.from"),
|
||||
Host: viper.GetString("smtp.host"),
|
||||
Port: viper.GetInt("smtp.port"),
|
||||
Username: viper.GetString("smtp.username"),
|
||||
Password: viper.GetString("smtp.password"),
|
||||
InsecureSkipVerify: viper.GetBool("smtp.skip_verify"),
|
||||
}, security)
|
||||
r, _ := route.NewRouter(db, appConfig)
|
||||
log.WithFields(log.Fields{"url": viper.GetString("server.hostname")}).Info("starting server")
|
||||
return http.ListenAndServe(viper.GetString("server.hostname"), r)
|
||||
},
|
||||
}
|
||||
|
||||
viper.SetDefault("smtp.from", "no-reply@example.com")
|
||||
viper.SetDefault("smtp.host", "localhost")
|
||||
viper.SetDefault("smtp.port", 587)
|
||||
viper.SetDefault("smtp.username", "")
|
||||
viper.SetDefault("smtp.password", "")
|
||||
viper.SetDefault("smtp.skip_verify", false)
|
||||
|
||||
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)
|
||||
return cc
|
||||
}
|
||||
|
129
internal/config/config.go
Normal file
129
internal/config/config.go
Normal file
@ -0,0 +1,129 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const (
|
||||
ServerHostname = "server.hostname"
|
||||
DatabaseHost = "database.host"
|
||||
DatabaseName = "database.name"
|
||||
DatabaseUser = "database.user"
|
||||
DatabasePassword = "database.password"
|
||||
DatabasePort = "database.port"
|
||||
DatabaseSslMode = "database.sslmode"
|
||||
|
||||
SecurityTokenExpiration = "security.token_expiration"
|
||||
SecuritySecret = "security.secret"
|
||||
|
||||
QueueBroker = "queue.broker"
|
||||
QueueStore = "queue.store"
|
||||
|
||||
SmtpFrom = "smtp.from"
|
||||
SmtpHost = "smtp.host"
|
||||
SmtpPort = "smtp.port"
|
||||
SmtpUsername = "smtp.username"
|
||||
SmtpPassword = "smtp.password"
|
||||
SmtpSkipVerify = "false"
|
||||
)
|
||||
|
||||
var defaults = map[string]string{
|
||||
ServerHostname: "0.0.0.0:3333",
|
||||
DatabaseHost: "127.0.0.1",
|
||||
DatabaseName: "taskcafe",
|
||||
DatabaseUser: "taskcafe",
|
||||
DatabasePassword: "taskcafe_test",
|
||||
DatabasePort: "5432",
|
||||
DatabaseSslMode: "disable",
|
||||
SecurityTokenExpiration: "15m",
|
||||
SecuritySecret: "",
|
||||
QueueBroker: "amqp://guest:guest@localhost:5672/",
|
||||
QueueStore: "memcache://localhost:11211",
|
||||
SmtpFrom: "no-reply@example.com",
|
||||
SmtpHost: "localhost",
|
||||
SmtpPort: "587",
|
||||
SmtpUsername: "",
|
||||
SmtpPassword: "",
|
||||
SmtpSkipVerify: "false",
|
||||
}
|
||||
|
||||
func InitDefaults() {
|
||||
for key, value := range defaults {
|
||||
viper.SetDefault(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
func GetDatabaseConnectionUri() string {
|
||||
connection := fmt.Sprintf("user=%s password=%s host=%s dbname=%s port=%s sslmode=%s",
|
||||
viper.GetString(DatabaseUser),
|
||||
viper.GetString(DatabasePassword),
|
||||
viper.GetString(DatabaseHost),
|
||||
viper.GetString(DatabaseName),
|
||||
viper.GetString(DatabasePort),
|
||||
viper.GetString(DatabaseSslMode),
|
||||
)
|
||||
return connection
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
Email EmailConfig
|
||||
Security SecurityConfig
|
||||
}
|
||||
|
||||
type EmailConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
From string
|
||||
Username string
|
||||
Password string
|
||||
SiteURL string
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
type SecurityConfig struct {
|
||||
AccessTokenExpiration time.Duration
|
||||
Secret []byte
|
||||
}
|
||||
|
||||
func GetAppConfig() (AppConfig, error) {
|
||||
secret := viper.GetString(SecuritySecret)
|
||||
if strings.TrimSpace(secret) == "" {
|
||||
log.Warn("server.secret is not set, generating a random secret")
|
||||
secret = uuid.New().String()
|
||||
}
|
||||
securityCfg, err := GetSecurityConfig(viper.GetString(SecurityTokenExpiration), []byte(secret))
|
||||
if err != nil {
|
||||
return AppConfig{}, err
|
||||
}
|
||||
emailCfg := GetEmailConfig()
|
||||
return AppConfig{
|
||||
Email: emailCfg,
|
||||
Security: securityCfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func GetSecurityConfig(accessTokenExp string, secret []byte) (SecurityConfig, error) {
|
||||
exp, err := time.ParseDuration(accessTokenExp)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("issue parsing duration")
|
||||
return SecurityConfig{}, err
|
||||
}
|
||||
return SecurityConfig{AccessTokenExpiration: exp, Secret: secret}, nil
|
||||
}
|
||||
|
||||
func GetEmailConfig() EmailConfig {
|
||||
return EmailConfig{
|
||||
From: viper.GetString("smtp.from"),
|
||||
Host: viper.GetString("smtp.host"),
|
||||
Port: viper.GetInt("smtp.port"),
|
||||
Username: viper.GetString("smtp.username"),
|
||||
Password: viper.GetString("smtp.password"),
|
||||
InsecureSkipVerify: viper.GetBool("smtp.skip_verify"),
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/99designs/gqlgen/graphql/handler/transport"
|
||||
"github.com/99designs/gqlgen/graphql/playground"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/db"
|
||||
"github.com/jordanknott/taskcafe/internal/logger"
|
||||
"github.com/jordanknott/taskcafe/internal/utils"
|
||||
@ -25,11 +26,11 @@ import (
|
||||
)
|
||||
|
||||
// NewHandler returns a new graphql endpoint handler.
|
||||
func NewHandler(repo db.Repository, emailConfig utils.EmailConfig) http.Handler {
|
||||
func NewHandler(repo db.Repository, appConfig config.AppConfig) http.Handler {
|
||||
c := Config{
|
||||
Resolvers: &Resolver{
|
||||
Repository: repo,
|
||||
EmailConfig: emailConfig,
|
||||
Repository: repo,
|
||||
AppConfig: appConfig,
|
||||
},
|
||||
}
|
||||
c.Directives.HasRole = func(ctx context.Context, obj interface{}, next graphql.Resolver, roles []RoleLevel, level ActionLevel, typeArg ObjectType) (interface{}, error) {
|
||||
|
@ -130,7 +130,7 @@ func (r *mutationResolver) InviteProjectMembers(ctx context.Context, input Invit
|
||||
return &InviteProjectMembersPayload{Ok: false}, err
|
||||
}
|
||||
invite := utils.EmailInvite{To: *invitedMember.Email, FullName: *invitedMember.Email, ConfirmToken: confirmToken.ConfirmTokenID.String()}
|
||||
err = utils.SendEmailInvite(r.EmailConfig, invite)
|
||||
err = utils.SendEmailInvite(r.AppConfig.Email, invite)
|
||||
if err != nil {
|
||||
logger.New(ctx).WithError(err).Error("issue sending email")
|
||||
return &InviteProjectMembersPayload{Ok: false}, err
|
||||
|
@ -6,13 +6,13 @@ package graph
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/db"
|
||||
"github.com/jordanknott/taskcafe/internal/utils"
|
||||
)
|
||||
|
||||
// Resolver handles resolving GraphQL queries & mutations
|
||||
type Resolver struct {
|
||||
Repository db.Repository
|
||||
EmailConfig utils.EmailConfig
|
||||
mu sync.Mutex
|
||||
Repository db.Repository
|
||||
AppConfig config.AppConfig
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
@ -13,11 +13,11 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
"github.com/jordanknott/taskcafe/internal/db"
|
||||
"github.com/jordanknott/taskcafe/internal/frontend"
|
||||
"github.com/jordanknott/taskcafe/internal/graph"
|
||||
"github.com/jordanknott/taskcafe/internal/logger"
|
||||
"github.com/jordanknott/taskcafe/internal/utils"
|
||||
)
|
||||
|
||||
// FrontendHandler serves an embed React client through chi
|
||||
@ -61,12 +61,12 @@ func (h FrontendHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// TaskcafeHandler contains all the route handlers
|
||||
type TaskcafeHandler struct {
|
||||
repo db.Repository
|
||||
SecurityConfig utils.SecurityConfig
|
||||
repo db.Repository
|
||||
AppConfig config.AppConfig
|
||||
}
|
||||
|
||||
// NewRouter creates a new router for chi
|
||||
func NewRouter(dbConnection *sqlx.DB, emailConfig utils.EmailConfig, securityConfig utils.SecurityConfig) (chi.Router, error) {
|
||||
func NewRouter(dbConnection *sqlx.DB, appConfig config.AppConfig) (chi.Router, error) {
|
||||
formatter := new(log.TextFormatter)
|
||||
formatter.TimestampFormat = "02-01-2006 15:04:05"
|
||||
formatter.FullTimestamp = true
|
||||
@ -93,7 +93,7 @@ func NewRouter(dbConnection *sqlx.DB, emailConfig utils.EmailConfig, securityCon
|
||||
}))
|
||||
|
||||
repository := db.NewRepository(dbConnection)
|
||||
taskcafeHandler := TaskcafeHandler{*repository, securityConfig}
|
||||
taskcafeHandler := TaskcafeHandler{*repository, appConfig}
|
||||
|
||||
var imgServer = http.FileServer(http.Dir("./uploads/"))
|
||||
r.Group(func(mux chi.Router) {
|
||||
@ -109,7 +109,7 @@ func NewRouter(dbConnection *sqlx.DB, emailConfig utils.EmailConfig, securityCon
|
||||
r.Group(func(mux chi.Router) {
|
||||
mux.Use(auth.Middleware)
|
||||
mux.Post("/users/me/avatar", taskcafeHandler.ProfileImageUpload)
|
||||
mux.Handle("/graphql", graph.NewHandler(*repository, emailConfig))
|
||||
mux.Handle("/graphql", graph.NewHandler(*repository, appConfig))
|
||||
})
|
||||
|
||||
frontend := FrontendHandler{staticPath: "build", indexPath: "index.html"}
|
||||
|
@ -3,31 +3,22 @@ package utils
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/jordanknott/taskcafe/internal/config"
|
||||
hermes "github.com/matcornic/hermes/v2"
|
||||
gomail "gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
type EmailConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
From string
|
||||
Username string
|
||||
Password string
|
||||
SiteURL string
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
type EmailInvite struct {
|
||||
ConfirmToken string
|
||||
FullName string
|
||||
To string
|
||||
}
|
||||
|
||||
func SendEmailInvite(config EmailConfig, invite EmailInvite) error {
|
||||
func SendEmailInvite(cfg config.EmailConfig, invite EmailInvite) error {
|
||||
h := hermes.Hermes{
|
||||
Product: hermes.Product{
|
||||
Name: "Taskscafe",
|
||||
Link: config.SiteURL,
|
||||
Link: cfg.SiteURL,
|
||||
Logo: "https://github.com/JordanKnott/taskcafe/raw/master/.github/taskcafe-full.png",
|
||||
},
|
||||
}
|
||||
@ -45,7 +36,7 @@ func SendEmailInvite(config EmailConfig, invite EmailInvite) error {
|
||||
Color: "#7367F0", // Optional action button color
|
||||
TextColor: "#FFFFFF",
|
||||
Text: "Register your account",
|
||||
Link: config.SiteURL + "/register?confirmToken=" + invite.ConfirmToken,
|
||||
Link: cfg.SiteURL + "/register?confirmToken=" + invite.ConfirmToken,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -67,7 +58,7 @@ func SendEmailInvite(config EmailConfig, invite EmailInvite) error {
|
||||
m := gomail.NewMessage()
|
||||
|
||||
// Set E-Mail sender
|
||||
m.SetHeader("From", config.From)
|
||||
m.SetHeader("From", cfg.From)
|
||||
|
||||
// Set E-Mail receivers
|
||||
m.SetHeader("To", invite.To)
|
||||
@ -80,11 +71,11 @@ func SendEmailInvite(config EmailConfig, invite EmailInvite) error {
|
||||
m.AddAlternative("text/plain", emailBodyPlain)
|
||||
|
||||
// Settings for SMTP server
|
||||
d := gomail.NewDialer(config.Host, config.Port, config.Username, config.Password)
|
||||
d := gomail.NewDialer(cfg.Host, cfg.Port, cfg.Username, cfg.Password)
|
||||
|
||||
// This is only needed when SSL/TLS certificate is not valid on server.
|
||||
// In production this should be set to false.
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: config.InsecureSkipVerify}
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: cfg.InsecureSkipVerify}
|
||||
|
||||
// Now send E-Mail
|
||||
if err := d.DialAndSend(m); err != nil {
|
||||
|
@ -1,21 +0,0 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type SecurityConfig struct {
|
||||
AccessTokenExpiration time.Duration
|
||||
Secret []byte
|
||||
}
|
||||
|
||||
func GetSecurityConfig(accessTokenExp string, secret []byte) (SecurityConfig, error) {
|
||||
exp, err := time.ParseDuration(accessTokenExp)
|
||||
if err != nil {
|
||||
log.WithError(err).Error("issue parsing duration")
|
||||
return SecurityConfig{}, err
|
||||
}
|
||||
return SecurityConfig{AccessTokenExpiration: exp, Secret: secret}, nil
|
||||
}
|
Loading…
Reference in New Issue
Block a user