diff --git a/cli/cli.go b/cli/cli.go index 623b9bbb..ae94aba2 100644 --- a/cli/cli.go +++ b/cli/cli.go @@ -51,9 +51,11 @@ func Parse() { flag.BoolVar(&flagDebugMode, "debug", false, flagDebugModeHelp) flag.Parse() - cfg := config.NewConfig() + if err := config.ParseConfig(); err != nil { + logger.Fatal("%v", err) + } - if flagDebugMode || cfg.HasDebugMode() { + if flagDebugMode || config.Opts.HasDebugMode() { logger.EnableDebug() } @@ -67,7 +69,15 @@ func Parse() { return } - db, err := database.NewConnectionPool(cfg.DatabaseURL(), cfg.DatabaseMinConns(), cfg.DatabaseMaxConns()) + if config.Opts.IsDefaultDatabaseURL() { + logger.Info("The default value for DATABASE_URL is used") + } + + db, err := database.NewConnectionPool( + config.Opts.DatabaseURL(), + config.Opts.DatabaseMinConns(), + config.Opts.DatabaseMaxConns(), + ) if err != nil { logger.Fatal("Unable to connect to the database: %v", err) } @@ -101,14 +111,14 @@ func Parse() { } // Run migrations and start the deamon. - if cfg.RunMigrations() { + if config.Opts.RunMigrations() { database.Migrate(db) } // Create admin user and start the deamon. - if cfg.CreateAdmin() { + if config.Opts.CreateAdmin() { createAdmin(store) } - startDaemon(cfg, store) + startDaemon(store) } diff --git a/cli/daemon.go b/cli/daemon.go index aa673d45..dc120874 100644 --- a/cli/daemon.go +++ b/cli/daemon.go @@ -16,13 +16,13 @@ import ( "miniflux.app/config" "miniflux.app/logger" "miniflux.app/reader/feed" - "miniflux.app/service/scheduler" "miniflux.app/service/httpd" + "miniflux.app/service/scheduler" "miniflux.app/storage" "miniflux.app/worker" ) -func startDaemon(cfg *config.Config, store *storage.Storage) { +func startDaemon(store *storage.Storage) { logger.Info("Starting Miniflux...") stop := make(chan os.Signal, 1) @@ -30,17 +30,17 @@ func startDaemon(cfg *config.Config, store *storage.Storage) { signal.Notify(stop, syscall.SIGTERM) feedHandler := feed.NewFeedHandler(store) - pool := worker.NewPool(feedHandler, cfg.WorkerPoolSize()) + pool := worker.NewPool(feedHandler, config.Opts.WorkerPoolSize()) go showProcessStatistics() - if cfg.HasSchedulerService() { - scheduler.Serve(cfg, store, pool) + if config.Opts.HasSchedulerService() { + scheduler.Serve(store, pool) } var httpServer *http.Server - if cfg.HasHTTPService() { - httpServer = httpd.Serve(cfg, store, pool, feedHandler) + if config.Opts.HasHTTPService() { + httpServer = httpd.Serve(store, pool, feedHandler) } <-stop @@ -64,4 +64,4 @@ func showProcessStatistics() { runtime.NumGoroutine(), runtime.NumCPU()) time.Sleep(30 * time.Second) } -} \ No newline at end of file +} diff --git a/config/config.go b/config/config.go index 99afaef9..53bd7085 100644 --- a/config/config.go +++ b/config/config.go @@ -4,271 +4,11 @@ package config // import "miniflux.app/config" -import ( - "net/url" - "os" - "strconv" - "strings" +// Opts contains configuration options after parsing. +var Opts *Options - "miniflux.app/logger" -) - -const ( - defaultBaseURL = "http://localhost" - defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" - defaultWorkerPoolSize = 5 - defaultPollingFrequency = 60 - defaultBatchSize = 10 - defaultDatabaseMaxConns = 20 - defaultDatabaseMinConns = 1 - defaultArchiveReadDays = 60 - defaultListenAddr = "127.0.0.1:8080" - defaultCertFile = "" - defaultKeyFile = "" - defaultCertDomain = "" - defaultCertCache = "/tmp/cert_cache" - defaultCleanupFrequency = 24 - defaultProxyImages = "http-only" - defaultOAuth2ClientID = "" - defaultOAuth2ClientSecret = "" - defaultOAuth2RedirectURL = "" - defaultOAuth2Provider = "" -) - -// Config manages configuration parameters. -type Config struct { - IsHTTPS bool - baseURL string - rootURL string - basePath string -} - -func (c *Config) parseBaseURL() { - baseURL := os.Getenv("BASE_URL") - if baseURL == "" { - return - } - - if baseURL[len(baseURL)-1:] == "/" { - baseURL = baseURL[:len(baseURL)-1] - } - - u, err := url.Parse(baseURL) - if err != nil { - logger.Error("Invalid BASE_URL: %v", err) - return - } - - scheme := strings.ToLower(u.Scheme) - if scheme != "https" && scheme != "http" { - logger.Error("Invalid BASE_URL: scheme must be http or https") - return - } - - c.baseURL = baseURL - c.basePath = u.Path - - u.Path = "" - c.rootURL = u.String() -} - -// HasDebugMode returns true if debug mode is enabled. -func (c *Config) HasDebugMode() bool { - return getBooleanValue("DEBUG") -} - -// BaseURL returns the application base URL with path. -func (c *Config) BaseURL() string { - return c.baseURL -} - -// RootURL returns the base URL without path. -func (c *Config) RootURL() string { - return c.rootURL -} - -// BasePath returns the application base path according to the base URL. -func (c *Config) BasePath() string { - return c.basePath -} - -// DatabaseURL returns the database URL. -func (c *Config) DatabaseURL() string { - value, exists := os.LookupEnv("DATABASE_URL") - if !exists { - logger.Info("The environment variable DATABASE_URL is not configured (the default value is used instead)") - } - - if value == "" { - value = defaultDatabaseURL - } - - return value -} - -// DatabaseMaxConns returns the maximum number of database connections. -func (c *Config) DatabaseMaxConns() int { - return getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns) -} - -// DatabaseMinConns returns the minimum number of database connections. -func (c *Config) DatabaseMinConns() int { - return getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns) -} - -// ListenAddr returns the listen address for the HTTP server. -func (c *Config) ListenAddr() string { - if port := os.Getenv("PORT"); port != "" { - return ":" + port - } - - return getStringValue("LISTEN_ADDR", defaultListenAddr) -} - -// CertFile returns the SSL certificate filename if any. -func (c *Config) CertFile() string { - return getStringValue("CERT_FILE", defaultCertFile) -} - -// KeyFile returns the private key filename for custom SSL certificate. -func (c *Config) KeyFile() string { - return getStringValue("KEY_FILE", defaultKeyFile) -} - -// CertDomain returns the domain to use for Let's Encrypt certificate. -func (c *Config) CertDomain() string { - return getStringValue("CERT_DOMAIN", defaultCertDomain) -} - -// CertCache returns the directory to use for Let's Encrypt session cache. -func (c *Config) CertCache() string { - return getStringValue("CERT_CACHE", defaultCertCache) -} - -// CleanupFrequency returns the interval for cleanup jobs. -func (c *Config) CleanupFrequency() int { - return getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency) -} - -// WorkerPoolSize returns the number of background worker. -func (c *Config) WorkerPoolSize() int { - return getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize) -} - -// PollingFrequency returns the interval to refresh feeds in the background. -func (c *Config) PollingFrequency() int { - return getIntValue("POLLING_FREQUENCY", defaultPollingFrequency) -} - -// BatchSize returns the number of feeds to send for background processing. -func (c *Config) BatchSize() int { - return getIntValue("BATCH_SIZE", defaultBatchSize) -} - -// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users. -func (c *Config) IsOAuth2UserCreationAllowed() bool { - return getBooleanValue("OAUTH2_USER_CREATION") -} - -// OAuth2ClientID returns the OAuth2 Client ID. -func (c *Config) OAuth2ClientID() string { - return getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID) -} - -// OAuth2ClientSecret returns the OAuth2 client secret. -func (c *Config) OAuth2ClientSecret() string { - return getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret) -} - -// OAuth2RedirectURL returns the OAuth2 redirect URL. -func (c *Config) OAuth2RedirectURL() string { - return getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL) -} - -// OAuth2Provider returns the name of the OAuth2 provider configured. -func (c *Config) OAuth2Provider() string { - return getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider) -} - -// HasHSTS returns true if HTTP Strict Transport Security is enabled. -func (c *Config) HasHSTS() bool { - return !getBooleanValue("DISABLE_HSTS") -} - -// RunMigrations returns true if the environment variable RUN_MIGRATIONS is not empty. -func (c *Config) RunMigrations() bool { - return getBooleanValue("RUN_MIGRATIONS") -} - -// CreateAdmin returns true if the environment variable CREATE_ADMIN is not empty. -func (c *Config) CreateAdmin() bool { - return getBooleanValue("CREATE_ADMIN") -} - -// PocketConsumerKey returns the Pocket Consumer Key if defined as environment variable. -func (c *Config) PocketConsumerKey(defaultValue string) string { - return getStringValue("POCKET_CONSUMER_KEY", defaultValue) -} - -// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy. -func (c *Config) ProxyImages() string { - return getStringValue("PROXY_IMAGES", defaultProxyImages) -} - -// HasHTTPService returns true if the HTTP service is enabled. -func (c *Config) HasHTTPService() bool { - return !getBooleanValue("DISABLE_HTTP_SERVICE") -} - -// HasSchedulerService returns true if the scheduler service is enabled. -func (c *Config) HasSchedulerService() bool { - return !getBooleanValue("DISABLE_SCHEDULER_SERVICE") -} - -// ArchiveReadDays returns the number of days after which marking read items as removed. -func (c *Config) ArchiveReadDays() int { - return getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays) -} - -// NewConfig returns a new Config. -func NewConfig() *Config { - cfg := &Config{ - baseURL: defaultBaseURL, - rootURL: defaultBaseURL, - IsHTTPS: getBooleanValue("HTTPS"), - } - - cfg.parseBaseURL() - return cfg -} - -func getBooleanValue(key string) bool { - value := strings.ToLower(os.Getenv(key)) - if value == "1" || value == "yes" || value == "true" || value == "on" { - return true - } - return false -} - -func getStringValue(key, fallback string) string { - value := os.Getenv(key) - if value == "" { - return fallback - } - - return value -} - -func getIntValue(key string, fallback int) int { - value := os.Getenv(key) - if value == "" { - return fallback - } - - v, err := strconv.Atoi(value) - if err != nil { - return fallback - } - - return v +// ParseConfig parses configuration options. +func ParseConfig() (err error) { + Opts, err = parse() + return err } diff --git a/config/config_test.go b/config/config_test.go index dc6112eb..0e691fc0 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -1,4 +1,4 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. +// Copyright 2019 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. @@ -9,198 +9,125 @@ import ( "testing" ) -func TestGetBooleanValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getBooleanValue("MY_TEST_VARIABLE") { - t.Errorf(`Unset variables should returns false`) - } -} - -func TestGetBooleanValue(t *testing.T) { - scenarios := map[string]bool{ - "": false, - "1": true, - "Yes": true, - "yes": true, - "True": true, - "true": true, - "on": true, - "false": false, - "off": false, - "invalid": false, - } - - for input, expected := range scenarios { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", input) - result := getBooleanValue("MY_TEST_VARIABLE") - if result != expected { - t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected) - } - } -} - -func TestGetStringValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "defaultValue" { - t.Errorf(`Unset variables should returns the default value`) - } -} - -func TestGetStringValue(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "test") - if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "test" { - t.Errorf(`Defined variables should returns the specified value`) - } -} - -func TestGetIntValueWithUnsetVariable(t *testing.T) { - os.Clearenv() - if getIntValue("MY_TEST_VARIABLE", 42) != 42 { - t.Errorf(`Unset variables should returns the default value`) - } -} - -func TestGetIntValueWithInvalidInput(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "invalid integer") - if getIntValue("MY_TEST_VARIABLE", 42) != 42 { - t.Errorf(`Invalid integer should returns the default value`) - } -} - -func TestGetIntValue(t *testing.T) { - os.Clearenv() - os.Setenv("MY_TEST_VARIABLE", "2018") - if getIntValue("MY_TEST_VARIABLE", 42) != 2018 { - t.Errorf(`Defined variables should returns the specified value`) - } -} - func TestDebugModeOn(t *testing.T) { os.Clearenv() os.Setenv("DEBUG", "1") - cfg := NewConfig() - if !cfg.HasDebugMode() { - t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + + if !opts.HasDebugMode() { + t.Fatalf(`Unexpected debug mode value, got "%v"`, opts.HasDebugMode()) } } func TestDebugModeOff(t *testing.T) { os.Clearenv() - cfg := NewConfig() - if cfg.HasDebugMode() { - t.Fatalf(`Unexpected debug mode value, got "%v"`, cfg.HasDebugMode()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + + if opts.HasDebugMode() { + t.Fatalf(`Unexpected debug mode value, got "%v"`, opts.HasDebugMode()) } } func TestCustomBaseURL(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example.org") - cfg := NewConfig() - if cfg.BaseURL() != "http://example.org" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } - if cfg.RootURL() != "http://example.org" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) + if opts.BaseURL() != "http://example.org" { + t.Fatalf(`Unexpected base URL, got "%s"`, opts.BaseURL()) } - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + if opts.RootURL() != "http://example.org" { + t.Fatalf(`Unexpected root URL, got "%s"`, opts.RootURL()) + } + + if opts.BasePath() != "" { + t.Fatalf(`Unexpected base path, got "%s"`, opts.BasePath()) } } func TestCustomBaseURLWithTrailingSlash(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example.org/folder/") - cfg := NewConfig() - if cfg.BaseURL() != "http://example.org/folder" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } - if cfg.RootURL() != "http://example.org" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) + if opts.BaseURL() != "http://example.org/folder" { + t.Fatalf(`Unexpected base URL, got "%s"`, opts.BaseURL()) } - if cfg.BasePath() != "/folder" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + if opts.RootURL() != "http://example.org" { + t.Fatalf(`Unexpected root URL, got "%s"`, opts.RootURL()) + } + + if opts.BasePath() != "/folder" { + t.Fatalf(`Unexpected base path, got "%s"`, opts.BasePath()) } } func TestBaseURLWithoutScheme(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "example.org/folder/") - cfg := NewConfig() - if cfg.BaseURL() != "http://localhost" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) - } - - if cfg.RootURL() != "http://localhost" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) - } - - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + _, err := parse() + if err == nil { + t.Fatalf(`Parsing must fail`) } } func TestBaseURLWithInvalidScheme(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "ftp://example.org/folder/") - cfg := NewConfig() - if cfg.BaseURL() != "http://localhost" { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) - } - - if cfg.RootURL() != "http://localhost" { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) - } - - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + _, err := parse() + if err == nil { + t.Fatalf(`Parsing must fail`) } } func TestInvalidBaseURL(t *testing.T) { os.Clearenv() os.Setenv("BASE_URL", "http://example|org") - cfg := NewConfig() - if cfg.BaseURL() != defaultBaseURL { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) - } - - if cfg.RootURL() != defaultBaseURL { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) - } - - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + _, err := parse() + if err == nil { + t.Fatalf(`Parsing must fail`) } } func TestDefaultBaseURL(t *testing.T) { os.Clearenv() - cfg := NewConfig() - if cfg.BaseURL() != defaultBaseURL { - t.Fatalf(`Unexpected base URL, got "%s"`, cfg.BaseURL()) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) } - if cfg.RootURL() != defaultBaseURL { - t.Fatalf(`Unexpected root URL, got "%s"`, cfg.RootURL()) + if opts.BaseURL() != defaultBaseURL { + t.Fatalf(`Unexpected base URL, got "%s"`, opts.BaseURL()) } - if cfg.BasePath() != "" { - t.Fatalf(`Unexpected base path, got "%s"`, cfg.BasePath()) + if opts.RootURL() != defaultBaseURL { + t.Fatalf(`Unexpected root URL, got "%s"`, opts.RootURL()) + } + + if opts.BasePath() != "" { + t.Fatalf(`Unexpected base path, got "%s"`, opts.BasePath()) } } @@ -208,9 +135,13 @@ func TestDatabaseURL(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_URL", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.DatabaseURL() + result := opts.DatabaseURL() if result != expected { t.Fatalf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) @@ -219,9 +150,14 @@ func TestDatabaseURL(t *testing.T) { func TestDefaultDatabaseURLValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.DatabaseURL() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultDatabaseURL + result := opts.DatabaseURL() if result != expected { t.Fatalf(`Unexpected DATABASE_URL value, got %q instead of %q`, result, expected) @@ -231,22 +167,30 @@ func TestDefaultDatabaseURLValue(t *testing.T) { func TestDefaultDatabaseMaxConnsValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultDatabaseMaxConns - result := cfg.DatabaseMaxConns() + result := opts.DatabaseMaxConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MAX_CONNS value, got %v instead of %v`, result, expected) } } -func TestDeatabaseMaxConns(t *testing.T) { +func TestDatabaseMaxConns(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_MAX_CONNS", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.DatabaseMaxConns() + result := opts.DatabaseMaxConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MAX_CONNS value, got %v instead of %v`, result, expected) @@ -256,9 +200,13 @@ func TestDeatabaseMaxConns(t *testing.T) { func TestDefaultDatabaseMinConnsValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultDatabaseMinConns - result := cfg.DatabaseMinConns() + result := opts.DatabaseMinConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MIN_CONNS value, got %v instead of %v`, result, expected) @@ -269,9 +217,13 @@ func TestDatabaseMinConns(t *testing.T) { os.Clearenv() os.Setenv("DATABASE_MIN_CONNS", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.DatabaseMinConns() + result := opts.DatabaseMinConns() if result != expected { t.Fatalf(`Unexpected DATABASE_MIN_CONNS value, got %v instead of %v`, result, expected) @@ -282,9 +234,13 @@ func TestListenAddr(t *testing.T) { os.Clearenv() os.Setenv("LISTEN_ADDR", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.ListenAddr() + result := opts.ListenAddr() if result != expected { t.Fatalf(`Unexpected LISTEN_ADDR value, got %q instead of %q`, result, expected) @@ -296,9 +252,13 @@ func TestListenAddrWithPortDefined(t *testing.T) { os.Setenv("PORT", "3000") os.Setenv("LISTEN_ADDR", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := ":3000" - result := cfg.ListenAddr() + result := opts.ListenAddr() if result != expected { t.Fatalf(`Unexpected LISTEN_ADDR value, got %q instead of %q`, result, expected) @@ -307,9 +267,14 @@ func TestListenAddrWithPortDefined(t *testing.T) { func TestDefaultListenAddrValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.ListenAddr() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultListenAddr + result := opts.ListenAddr() if result != expected { t.Fatalf(`Unexpected LISTEN_ADDR value, got %q instead of %q`, result, expected) @@ -320,9 +285,13 @@ func TestCertFile(t *testing.T) { os.Clearenv() os.Setenv("CERT_FILE", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.CertFile() + result := opts.CertFile() if result != expected { t.Fatalf(`Unexpected CERT_FILE value, got %q instead of %q`, result, expected) @@ -331,9 +300,14 @@ func TestCertFile(t *testing.T) { func TestDefaultCertFileValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.CertFile() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCertFile + result := opts.CertFile() if result != expected { t.Fatalf(`Unexpected CERT_FILE value, got %q instead of %q`, result, expected) @@ -344,9 +318,13 @@ func TestKeyFile(t *testing.T) { os.Clearenv() os.Setenv("KEY_FILE", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.KeyFile() + result := opts.CertKeyFile() if result != expected { t.Fatalf(`Unexpected KEY_FILE value, got %q instead of %q`, result, expected) @@ -355,9 +333,14 @@ func TestKeyFile(t *testing.T) { func TestDefaultKeyFileValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.KeyFile() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultKeyFile + result := opts.CertKeyFile() if result != expected { t.Fatalf(`Unexpected KEY_FILE value, got %q instead of %q`, result, expected) @@ -368,9 +351,13 @@ func TestCertDomain(t *testing.T) { os.Clearenv() os.Setenv("CERT_DOMAIN", "example.org") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "example.org" - result := cfg.CertDomain() + result := opts.CertDomain() if result != expected { t.Fatalf(`Unexpected CERT_DOMAIN value, got %q instead of %q`, result, expected) @@ -379,9 +366,14 @@ func TestCertDomain(t *testing.T) { func TestDefaultCertDomainValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.CertDomain() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCertDomain + result := opts.CertDomain() if result != expected { t.Fatalf(`Unexpected CERT_DOMAIN value, got %q instead of %q`, result, expected) @@ -392,9 +384,13 @@ func TestCertCache(t *testing.T) { os.Clearenv() os.Setenv("CERT_CACHE", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.CertCache() + result := opts.CertCache() if result != expected { t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected) @@ -403,9 +399,14 @@ func TestCertCache(t *testing.T) { func TestDefaultCertCacheValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.CertCache() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCertCache + result := opts.CertCache() if result != expected { t.Fatalf(`Unexpected CERT_CACHE value, got %q instead of %q`, result, expected) @@ -415,9 +416,13 @@ func TestDefaultCertCacheValue(t *testing.T) { func TestDefaultCleanupFrequencyValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultCleanupFrequency - result := cfg.CleanupFrequency() + result := opts.CleanupFrequency() if result != expected { t.Fatalf(`Unexpected CLEANUP_FREQUENCY value, got %v instead of %v`, result, expected) @@ -428,9 +433,13 @@ func TestCleanupFrequency(t *testing.T) { os.Clearenv() os.Setenv("CLEANUP_FREQUENCY", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.CleanupFrequency() + result := opts.CleanupFrequency() if result != expected { t.Fatalf(`Unexpected CLEANUP_FREQUENCY value, got %v instead of %v`, result, expected) @@ -440,9 +449,13 @@ func TestCleanupFrequency(t *testing.T) { func TestDefaultWorkerPoolSizeValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultWorkerPoolSize - result := cfg.WorkerPoolSize() + result := opts.WorkerPoolSize() if result != expected { t.Fatalf(`Unexpected WORKER_POOL_SIZE value, got %v instead of %v`, result, expected) @@ -453,9 +466,13 @@ func TestWorkerPoolSize(t *testing.T) { os.Clearenv() os.Setenv("WORKER_POOL_SIZE", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.WorkerPoolSize() + result := opts.WorkerPoolSize() if result != expected { t.Fatalf(`Unexpected WORKER_POOL_SIZE value, got %v instead of %v`, result, expected) @@ -465,9 +482,13 @@ func TestWorkerPoolSize(t *testing.T) { func TestDefautPollingFrequencyValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultPollingFrequency - result := cfg.PollingFrequency() + result := opts.PollingFrequency() if result != expected { t.Fatalf(`Unexpected POLLING_FREQUENCY value, got %v instead of %v`, result, expected) @@ -478,9 +499,13 @@ func TestPollingFrequency(t *testing.T) { os.Clearenv() os.Setenv("POLLING_FREQUENCY", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.PollingFrequency() + result := opts.PollingFrequency() if result != expected { t.Fatalf(`Unexpected POLLING_FREQUENCY value, got %v instead of %v`, result, expected) @@ -490,9 +515,13 @@ func TestPollingFrequency(t *testing.T) { func TestDefaultBatchSizeValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultBatchSize - result := cfg.BatchSize() + result := opts.BatchSize() if result != expected { t.Fatalf(`Unexpected BATCH_SIZE value, got %v instead of %v`, result, expected) @@ -503,9 +532,13 @@ func TestBatchSize(t *testing.T) { os.Clearenv() os.Setenv("BATCH_SIZE", "42") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 42 - result := cfg.BatchSize() + result := opts.BatchSize() if result != expected { t.Fatalf(`Unexpected BATCH_SIZE value, got %v instead of %v`, result, expected) @@ -515,9 +548,13 @@ func TestBatchSize(t *testing.T) { func TestOAuth2UserCreationWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.IsOAuth2UserCreationAllowed() + result := opts.IsOAuth2UserCreationAllowed() if result != expected { t.Fatalf(`Unexpected OAUTH2_USER_CREATION value, got %v instead of %v`, result, expected) @@ -528,9 +565,13 @@ func TestOAuth2UserCreationAdmin(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_USER_CREATION", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.IsOAuth2UserCreationAllowed() + result := opts.IsOAuth2UserCreationAllowed() if result != expected { t.Fatalf(`Unexpected OAUTH2_USER_CREATION value, got %v instead of %v`, result, expected) @@ -541,9 +582,13 @@ func TestOAuth2ClientID(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_CLIENT_ID", "foobar") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "foobar" - result := cfg.OAuth2ClientID() + result := opts.OAuth2ClientID() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_ID value, got %q instead of %q`, result, expected) @@ -552,9 +597,14 @@ func TestOAuth2ClientID(t *testing.T) { func TestDefaultOAuth2ClientIDValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2ClientID() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2ClientID + result := opts.OAuth2ClientID() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_ID value, got %q instead of %q`, result, expected) @@ -565,9 +615,13 @@ func TestOAuth2ClientSecret(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_CLIENT_SECRET", "secret") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "secret" - result := cfg.OAuth2ClientSecret() + result := opts.OAuth2ClientSecret() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_SECRET value, got %q instead of %q`, result, expected) @@ -576,9 +630,14 @@ func TestOAuth2ClientSecret(t *testing.T) { func TestDefaultOAuth2ClientSecretValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2ClientSecret() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2ClientSecret + result := opts.OAuth2ClientSecret() if result != expected { t.Fatalf(`Unexpected OAUTH2_CLIENT_SECRET value, got %q instead of %q`, result, expected) @@ -589,9 +648,13 @@ func TestOAuth2RedirectURL(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_REDIRECT_URL", "http://example.org") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "http://example.org" - result := cfg.OAuth2RedirectURL() + result := opts.OAuth2RedirectURL() if result != expected { t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected) @@ -600,9 +663,14 @@ func TestOAuth2RedirectURL(t *testing.T) { func TestDefaultOAuth2RedirectURLValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2RedirectURL() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2RedirectURL + result := opts.OAuth2RedirectURL() if result != expected { t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected) @@ -613,9 +681,13 @@ func TestOAuth2Provider(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_PROVIDER", "google") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "google" - result := cfg.OAuth2Provider() + result := opts.OAuth2Provider() if result != expected { t.Fatalf(`Unexpected OAUTH2_PROVIDER value, got %q instead of %q`, result, expected) @@ -624,9 +696,14 @@ func TestOAuth2Provider(t *testing.T) { func TestDefaultOAuth2ProviderValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.OAuth2Provider() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultOAuth2Provider + result := opts.OAuth2Provider() if result != expected { t.Fatalf(`Unexpected OAUTH2_PROVIDER value, got %q instead of %q`, result, expected) @@ -636,9 +713,13 @@ func TestDefaultOAuth2ProviderValue(t *testing.T) { func TestHSTSWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.HasHSTS() + result := opts.HasHSTS() if result != expected { t.Fatalf(`Unexpected DISABLE_HSTS value, got %v instead of %v`, result, expected) @@ -649,9 +730,13 @@ func TestHSTS(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_HSTS", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.HasHSTS() + result := opts.HasHSTS() if result != expected { t.Fatalf(`Unexpected DISABLE_HSTS value, got %v instead of %v`, result, expected) @@ -661,9 +746,13 @@ func TestHSTS(t *testing.T) { func TestDisableHTTPServiceWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.HasHTTPService() + result := opts.HasHTTPService() if result != expected { t.Fatalf(`Unexpected DISABLE_HTTP_SERVICE value, got %v instead of %v`, result, expected) @@ -674,9 +763,13 @@ func TestDisableHTTPService(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_HTTP_SERVICE", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.HasHTTPService() + result := opts.HasHTTPService() if result != expected { t.Fatalf(`Unexpected DISABLE_HTTP_SERVICE value, got %v instead of %v`, result, expected) @@ -686,9 +779,13 @@ func TestDisableHTTPService(t *testing.T) { func TestDisableSchedulerServiceWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.HasSchedulerService() + result := opts.HasSchedulerService() if result != expected { t.Fatalf(`Unexpected DISABLE_SCHEDULER_SERVICE value, got %v instead of %v`, result, expected) @@ -699,9 +796,13 @@ func TestDisableSchedulerService(t *testing.T) { os.Clearenv() os.Setenv("DISABLE_SCHEDULER_SERVICE", "1") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.HasSchedulerService() + result := opts.HasSchedulerService() if result != expected { t.Fatalf(`Unexpected DISABLE_SCHEDULER_SERVICE value, got %v instead of %v`, result, expected) @@ -712,9 +813,13 @@ func TestArchiveReadDays(t *testing.T) { os.Clearenv() os.Setenv("ARCHIVE_READ_DAYS", "7") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := 7 - result := cfg.ArchiveReadDays() + result := opts.ArchiveReadDays() if result != expected { t.Fatalf(`Unexpected ARCHIVE_READ_DAYS value, got %v instead of %v`, result, expected) @@ -724,9 +829,13 @@ func TestArchiveReadDays(t *testing.T) { func TestRunMigrationsWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.RunMigrations() + result := opts.RunMigrations() if result != expected { t.Fatalf(`Unexpected RUN_MIGRATIONS value, got %v instead of %v`, result, expected) @@ -737,9 +846,13 @@ func TestRunMigrations(t *testing.T) { os.Clearenv() os.Setenv("RUN_MIGRATIONS", "yes") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.RunMigrations() + result := opts.RunMigrations() if result != expected { t.Fatalf(`Unexpected RUN_MIGRATIONS value, got %v instead of %v`, result, expected) @@ -749,9 +862,13 @@ func TestRunMigrations(t *testing.T) { func TestCreateAdminWhenUnset(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := false - result := cfg.CreateAdmin() + result := opts.CreateAdmin() if result != expected { t.Fatalf(`Unexpected CREATE_ADMIN value, got %v instead of %v`, result, expected) @@ -762,9 +879,13 @@ func TestCreateAdmin(t *testing.T) { os.Clearenv() os.Setenv("CREATE_ADMIN", "true") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := true - result := cfg.CreateAdmin() + result := opts.CreateAdmin() if result != expected { t.Fatalf(`Unexpected CREATE_ADMIN value, got %v instead of %v`, result, expected) @@ -775,9 +896,13 @@ func TestPocketConsumerKeyFromEnvVariable(t *testing.T) { os.Clearenv() os.Setenv("POCKET_CONSUMER_KEY", "something") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "something" - result := cfg.PocketConsumerKey("default") + result := opts.PocketConsumerKey("default") if result != expected { t.Fatalf(`Unexpected POCKET_CONSUMER_KEY value, got %q instead of %q`, result, expected) @@ -787,9 +912,13 @@ func TestPocketConsumerKeyFromEnvVariable(t *testing.T) { func TestPocketConsumerKeyFromUserPrefs(t *testing.T) { os.Clearenv() - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "default" - result := cfg.PocketConsumerKey("default") + result := opts.PocketConsumerKey("default") if result != expected { t.Fatalf(`Unexpected POCKET_CONSUMER_KEY value, got %q instead of %q`, result, expected) @@ -800,9 +929,13 @@ func TestProxyImages(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - cfg := NewConfig() + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := "all" - result := cfg.ProxyImages() + result := opts.ProxyImages() if result != expected { t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected) @@ -811,9 +944,14 @@ func TestProxyImages(t *testing.T) { func TestDefaultProxyImagesValue(t *testing.T) { os.Clearenv() - cfg := NewConfig() - result := cfg.ProxyImages() + + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + expected := defaultProxyImages + result := opts.ProxyImages() if result != expected { t.Fatalf(`Unexpected PROXY_IMAGES value, got %q instead of %q`, result, expected) @@ -822,19 +960,27 @@ func TestDefaultProxyImagesValue(t *testing.T) { func TestHTTPSOff(t *testing.T) { os.Clearenv() - cfg := NewConfig() - if cfg.IsHTTPS { - t.Fatalf(`Unexpected HTTPS value, got "%v"`, cfg.IsHTTPS) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + + if opts.HTTPS { + t.Fatalf(`Unexpected HTTPS value, got "%v"`, opts.HTTPS) } } func TestHTTPSOn(t *testing.T) { os.Clearenv() os.Setenv("HTTPS", "on") - cfg := NewConfig() - if !cfg.IsHTTPS { - t.Fatalf(`Unexpected HTTPS value, got "%v"`, cfg.IsHTTPS) + opts, err := parse() + if err != nil { + t.Fatalf(`Parsing failure: %q`, err) + } + + if !opts.HTTPS { + t.Fatalf(`Unexpected HTTPS value, got "%v"`, opts.HTTPS) } } diff --git a/config/doc.go b/config/doc.go index 4b4814b5..c8121acd 100644 --- a/config/doc.go +++ b/config/doc.go @@ -1,10 +1,10 @@ -// Copyright 2018 Frédéric Guillot. All rights reserved. +// Copyright 2019 Frédéric Guillot. All rights reserved. // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. /* -Package config handles configuration values for the application. +Package config handles configuration management for the application. */ package config // import "miniflux.app/config" diff --git a/config/options.go b/config/options.go new file mode 100644 index 00000000..ad8d2c7c --- /dev/null +++ b/config/options.go @@ -0,0 +1,214 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package config // import "miniflux.app/config" + +const ( + defaultBaseURL = "http://localhost" + defaultWorkerPoolSize = 5 + defaultPollingFrequency = 60 + defaultBatchSize = 10 + defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" + defaultDatabaseMaxConns = 20 + defaultDatabaseMinConns = 1 + defaultArchiveReadDays = 60 + defaultListenAddr = "127.0.0.1:8080" + defaultCertFile = "" + defaultKeyFile = "" + defaultCertDomain = "" + defaultCertCache = "/tmp/cert_cache" + defaultCleanupFrequency = 24 + defaultProxyImages = "http-only" + defaultOAuth2ClientID = "" + defaultOAuth2ClientSecret = "" + defaultOAuth2RedirectURL = "" + defaultOAuth2Provider = "" +) + +// Options contains configuration options. +type Options struct { + HTTPS bool + hsts bool + httpService bool + schedulerService bool + debug bool + baseURL string + rootURL string + basePath string + databaseURL string + databaseMaxConns int + databaseMinConns int + runMigrations bool + listenAddr string + certFile string + certDomain string + certCache string + certKeyFile string + cleanupFrequency int + archiveReadDays int + pollingFrequency int + batchSize int + workerPoolSize int + createAdmin bool + proxyImages string + oauth2UserCreationAllowed bool + oauth2ClientID string + oauth2ClientSecret string + oauth2RedirectURL string + oauth2Provider string + pocketConsumerKey string +} + +// HasDebugMode returns true if debug mode is enabled. +func (o *Options) HasDebugMode() bool { + return o.debug +} + +// BaseURL returns the application base URL with path. +func (o *Options) BaseURL() string { + return o.baseURL +} + +// RootURL returns the base URL without path. +func (o *Options) RootURL() string { + return o.rootURL +} + +// BasePath returns the application base path according to the base URL. +func (o *Options) BasePath() string { + return o.basePath +} + +// IsDefaultDatabaseURL returns true if the default database URL is used. +func (o *Options) IsDefaultDatabaseURL() bool { + return o.databaseURL == defaultDatabaseURL +} + +// DatabaseURL returns the database URL. +func (o *Options) DatabaseURL() string { + return o.databaseURL +} + +// DatabaseMaxConns returns the maximum number of database connections. +func (o *Options) DatabaseMaxConns() int { + return o.databaseMaxConns +} + +// DatabaseMinConns returns the minimum number of database connections. +func (o *Options) DatabaseMinConns() int { + return o.databaseMinConns +} + +// ListenAddr returns the listen address for the HTTP server. +func (o *Options) ListenAddr() string { + return o.listenAddr +} + +// CertFile returns the SSL certificate filename if any. +func (o *Options) CertFile() string { + return o.certFile +} + +// CertKeyFile returns the private key filename for custom SSL certificate. +func (o *Options) CertKeyFile() string { + return o.certKeyFile +} + +// CertDomain returns the domain to use for Let's Encrypt certificate. +func (o *Options) CertDomain() string { + return o.certDomain +} + +// CertCache returns the directory to use for Let's Encrypt session cache. +func (o *Options) CertCache() string { + return o.certCache +} + +// CleanupFrequency returns the interval for cleanup jobs. +func (o *Options) CleanupFrequency() int { + return o.cleanupFrequency +} + +// WorkerPoolSize returns the number of background worker. +func (o *Options) WorkerPoolSize() int { + return o.workerPoolSize +} + +// PollingFrequency returns the interval to refresh feeds in the background. +func (o *Options) PollingFrequency() int { + return o.pollingFrequency +} + +// BatchSize returns the number of feeds to send for background processing. +func (o *Options) BatchSize() int { + return o.batchSize +} + +// IsOAuth2UserCreationAllowed returns true if user creation is allowed for OAuth2 users. +func (o *Options) IsOAuth2UserCreationAllowed() bool { + return o.oauth2UserCreationAllowed +} + +// OAuth2ClientID returns the OAuth2 Client ID. +func (o *Options) OAuth2ClientID() string { + return o.oauth2ClientID +} + +// OAuth2ClientSecret returns the OAuth2 client secret. +func (o *Options) OAuth2ClientSecret() string { + return o.oauth2ClientSecret +} + +// OAuth2RedirectURL returns the OAuth2 redirect URL. +func (o *Options) OAuth2RedirectURL() string { + return o.oauth2RedirectURL +} + +// OAuth2Provider returns the name of the OAuth2 provider configured. +func (o *Options) OAuth2Provider() string { + return o.oauth2Provider +} + +// HasHSTS returns true if HTTP Strict Transport Security is enabled. +func (o *Options) HasHSTS() bool { + return o.hsts +} + +// RunMigrations returns true if the environment variable RUN_MIGRATIONS is not empty. +func (o *Options) RunMigrations() bool { + return o.runMigrations +} + +// CreateAdmin returns true if the environment variable CREATE_ADMIN is not empty. +func (o *Options) CreateAdmin() bool { + return o.createAdmin +} + +// ProxyImages returns "none" to never proxy, "http-only" to proxy non-HTTPS, "all" to always proxy. +func (o *Options) ProxyImages() string { + return o.proxyImages +} + +// HasHTTPService returns true if the HTTP service is enabled. +func (o *Options) HasHTTPService() bool { + return o.httpService +} + +// HasSchedulerService returns true if the scheduler service is enabled. +func (o *Options) HasSchedulerService() bool { + return o.schedulerService +} + +// ArchiveReadDays returns the number of days after which marking read items as removed. +func (o *Options) ArchiveReadDays() int { + return o.archiveReadDays +} + +// PocketConsumerKey returns the Pocket Consumer Key if configured. +func (o *Options) PocketConsumerKey(defaultValue string) string { + if o.pocketConsumerKey != "" { + return o.pocketConsumerKey + } + return defaultValue +} diff --git a/config/parser.go b/config/parser.go new file mode 100644 index 00000000..996d0a80 --- /dev/null +++ b/config/parser.go @@ -0,0 +1,124 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package config // import "miniflux.app/config" + +import ( + "errors" + "fmt" + "net/url" + "os" + "strconv" + "strings" +) + +func parse() (opts *Options, err error) { + opts = &Options{} + opts.baseURL, opts.rootURL, opts.basePath, err = parseBaseURL() + if err != nil { + return nil, err + } + + opts.debug = getBooleanValue("DEBUG") + opts.listenAddr = parseListenAddr() + + opts.databaseURL = getStringValue("DATABASE_URL", defaultDatabaseURL) + opts.databaseMaxConns = getIntValue("DATABASE_MAX_CONNS", defaultDatabaseMaxConns) + opts.databaseMinConns = getIntValue("DATABASE_MIN_CONNS", defaultDatabaseMinConns) + opts.runMigrations = getBooleanValue("RUN_MIGRATIONS") + + opts.hsts = !getBooleanValue("DISABLE_HSTS") + opts.HTTPS = getBooleanValue("HTTPS") + + opts.schedulerService = !getBooleanValue("DISABLE_SCHEDULER_SERVICE") + opts.httpService = !getBooleanValue("DISABLE_HTTP_SERVICE") + + opts.certFile = getStringValue("CERT_FILE", defaultCertFile) + opts.certKeyFile = getStringValue("KEY_FILE", defaultKeyFile) + opts.certDomain = getStringValue("CERT_DOMAIN", defaultCertDomain) + opts.certCache = getStringValue("CERT_CACHE", defaultCertCache) + + opts.cleanupFrequency = getIntValue("CLEANUP_FREQUENCY", defaultCleanupFrequency) + opts.workerPoolSize = getIntValue("WORKER_POOL_SIZE", defaultWorkerPoolSize) + opts.pollingFrequency = getIntValue("POLLING_FREQUENCY", defaultPollingFrequency) + opts.batchSize = getIntValue("BATCH_SIZE", defaultBatchSize) + opts.archiveReadDays = getIntValue("ARCHIVE_READ_DAYS", defaultArchiveReadDays) + opts.proxyImages = getStringValue("PROXY_IMAGES", defaultProxyImages) + + opts.oauth2UserCreationAllowed = getBooleanValue("OAUTH2_USER_CREATION") + opts.oauth2ClientID = getStringValue("OAUTH2_CLIENT_ID", defaultOAuth2ClientID) + opts.oauth2ClientSecret = getStringValue("OAUTH2_CLIENT_SECRET", defaultOAuth2ClientSecret) + opts.oauth2RedirectURL = getStringValue("OAUTH2_REDIRECT_URL", defaultOAuth2RedirectURL) + opts.oauth2Provider = getStringValue("OAUTH2_PROVIDER", defaultOAuth2Provider) + + opts.pocketConsumerKey = getStringValue("POCKET_CONSUMER_KEY", "") + + opts.createAdmin = getBooleanValue("CREATE_ADMIN") + + return opts, nil +} + +func parseBaseURL() (string, string, string, error) { + baseURL := os.Getenv("BASE_URL") + if baseURL == "" { + return defaultBaseURL, defaultBaseURL, "", nil + } + + if baseURL[len(baseURL)-1:] == "/" { + baseURL = baseURL[:len(baseURL)-1] + } + + u, err := url.Parse(baseURL) + if err != nil { + return "", "", "", fmt.Errorf("Invalid BASE_URL: %v", err) + } + + scheme := strings.ToLower(u.Scheme) + if scheme != "https" && scheme != "http" { + return "", "", "", errors.New("Invalid BASE_URL: scheme must be http or https") + } + + basePath := u.Path + u.Path = "" + return baseURL, u.String(), basePath, nil +} + +func parseListenAddr() string { + if port := os.Getenv("PORT"); port != "" { + return ":" + port + } + + return getStringValue("LISTEN_ADDR", defaultListenAddr) +} + +func getBooleanValue(key string) bool { + value := strings.ToLower(os.Getenv(key)) + if value == "1" || value == "yes" || value == "true" || value == "on" { + return true + } + return false +} + +func getStringValue(key, fallback string) string { + value := os.Getenv(key) + if value == "" { + return fallback + } + + return value +} + +func getIntValue(key string, fallback int) int { + value := os.Getenv(key) + if value == "" { + return fallback + } + + v, err := strconv.Atoi(value) + if err != nil { + return fallback + } + + return v +} diff --git a/config/parser_test.go b/config/parser_test.go new file mode 100644 index 00000000..ba454b4b --- /dev/null +++ b/config/parser_test.go @@ -0,0 +1,79 @@ +// Copyright 2019 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package config // import "miniflux.app/config" + +import ( + "os" + "testing" +) + +func TestGetBooleanValueWithUnsetVariable(t *testing.T) { + os.Clearenv() + if getBooleanValue("MY_TEST_VARIABLE") { + t.Errorf(`Unset variables should returns false`) + } +} + +func TestGetBooleanValue(t *testing.T) { + scenarios := map[string]bool{ + "": false, + "1": true, + "Yes": true, + "yes": true, + "True": true, + "true": true, + "on": true, + "false": false, + "off": false, + "invalid": false, + } + + for input, expected := range scenarios { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", input) + result := getBooleanValue("MY_TEST_VARIABLE") + if result != expected { + t.Errorf(`Unexpected result for %q, got %v instead of %v`, input, result, expected) + } + } +} + +func TestGetStringValueWithUnsetVariable(t *testing.T) { + os.Clearenv() + if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "defaultValue" { + t.Errorf(`Unset variables should returns the default value`) + } +} + +func TestGetStringValue(t *testing.T) { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", "test") + if getStringValue("MY_TEST_VARIABLE", "defaultValue") != "test" { + t.Errorf(`Defined variables should returns the specified value`) + } +} + +func TestGetIntValueWithUnsetVariable(t *testing.T) { + os.Clearenv() + if getIntValue("MY_TEST_VARIABLE", 42) != 42 { + t.Errorf(`Unset variables should returns the default value`) + } +} + +func TestGetIntValueWithInvalidInput(t *testing.T) { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", "invalid integer") + if getIntValue("MY_TEST_VARIABLE", 42) != 42 { + t.Errorf(`Invalid integer should returns the default value`) + } +} + +func TestGetIntValue(t *testing.T) { + os.Clearenv() + os.Setenv("MY_TEST_VARIABLE", "2018") + if getIntValue("MY_TEST_VARIABLE", 42) != 2018 { + t.Errorf(`Defined variables should returns the specified value`) + } +} diff --git a/fever/handler.go b/fever/handler.go index 95d0a44b..869e6210 100644 --- a/fever/handler.go +++ b/fever/handler.go @@ -10,7 +10,6 @@ import ( "strings" "time" - "miniflux.app/config" "miniflux.app/http/request" "miniflux.app/http/response/json" "miniflux.app/integration" @@ -22,8 +21,8 @@ import ( ) // Serve handles Fever API calls. -func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage) { - handler := &handler{cfg, store} +func Serve(router *mux.Router, store *storage.Storage) { + handler := &handler{store} sr := router.PathPrefix("/fever").Subrouter() sr.Use(newMiddleware(store).serve) @@ -31,7 +30,6 @@ func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage) { } type handler struct { - cfg *config.Config store *storage.Storage } @@ -424,7 +422,7 @@ func (h *handler) handleWriteItems(w http.ResponseWriter, r *http.Request) { } go func() { - integration.SendEntry(h.cfg, entry, settings) + integration.SendEntry(entry, settings) }() } diff --git a/integration/integration.go b/integration/integration.go index 38a215f9..90449d15 100644 --- a/integration/integration.go +++ b/integration/integration.go @@ -16,7 +16,7 @@ import ( ) // SendEntry send the entry to the activated providers. -func SendEntry(cfg *config.Config, entry *model.Entry, integration *model.Integration) { +func SendEntry(entry *model.Entry, integration *model.Integration) { if integration.PinboardEnabled { client := pinboard.NewClient(integration.PinboardToken) err := client.AddBookmark( @@ -64,7 +64,7 @@ func SendEntry(cfg *config.Config, entry *model.Entry, integration *model.Integr } if integration.PocketEnabled { - client := pocket.NewClient(cfg.PocketConsumerKey(integration.PocketConsumerKey), integration.PocketAccessToken) + client := pocket.NewClient(config.Opts.PocketConsumerKey(integration.PocketConsumerKey), integration.PocketAccessToken) if err := client.AddURL(entry.URL, entry.Title); err != nil { logger.Error("[Integration] UserID #%d: %v", integration.UserID, err) } diff --git a/service/httpd/httpd.go b/service/httpd/httpd.go index 2e930860..51399698 100644 --- a/service/httpd/httpd.go +++ b/service/httpd/httpd.go @@ -27,17 +27,17 @@ import ( ) // Serve starts a new HTTP server. -func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) *http.Server { - certFile := cfg.CertFile() - keyFile := cfg.KeyFile() - certDomain := cfg.CertDomain() - certCache := cfg.CertCache() - listenAddr := cfg.ListenAddr() +func Serve(store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) *http.Server { + certFile := config.Opts.CertFile() + keyFile := config.Opts.CertKeyFile() + certDomain := config.Opts.CertDomain() + certCache := config.Opts.CertCache() + listenAddr := config.Opts.ListenAddr() server := &http.Server{ ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, - Handler: setupHandler(cfg, store, feedHandler, pool), + Handler: setupHandler(store, feedHandler, pool), } switch { @@ -46,10 +46,10 @@ func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHa case strings.HasPrefix(listenAddr, "/"): startUnixSocketServer(server, listenAddr) case certDomain != "" && certCache != "": - cfg.IsHTTPS = true + config.Opts.HTTPS = true startAutoCertTLSServer(server, certDomain, certCache) case certFile != "" && keyFile != "": - cfg.IsHTTPS = true + config.Opts.HTTPS = true server.Addr = listenAddr startTLSServer(server, certFile, keyFile) default: @@ -156,18 +156,18 @@ func startHTTPServer(server *http.Server) { }() } -func setupHandler(cfg *config.Config, store *storage.Storage, feedHandler *feed.Handler, pool *worker.Pool) *mux.Router { +func setupHandler(store *storage.Storage, feedHandler *feed.Handler, pool *worker.Pool) *mux.Router { router := mux.NewRouter() - if cfg.BasePath() != "" { - router = router.PathPrefix(cfg.BasePath()).Subrouter() + if config.Opts.BasePath() != "" { + router = router.PathPrefix(config.Opts.BasePath()).Subrouter() } - router.Use(newMiddleware(cfg).Serve) + router.Use(middleware) - fever.Serve(router, cfg, store) + fever.Serve(router, store) api.Serve(router, store, feedHandler) - ui.Serve(router, cfg, store, pool, feedHandler) + ui.Serve(router, store, pool, feedHandler) router.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("OK")) diff --git a/service/httpd/middleware.go b/service/httpd/middleware.go index 9e5abc30..c169e08f 100644 --- a/service/httpd/middleware.go +++ b/service/httpd/middleware.go @@ -13,32 +13,24 @@ import ( "miniflux.app/logger" ) -type middleware struct { - cfg *config.Config -} - -func newMiddleware(cfg *config.Config) *middleware { - return &middleware{cfg} -} - -func (m *middleware) Serve(next http.Handler) http.Handler { +func middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { clientIP := request.FindClientIP(r) ctx := r.Context() ctx = context.WithValue(ctx, request.ClientIPContextKey, clientIP) if r.Header.Get("X-Forwarded-Proto") == "https" { - m.cfg.IsHTTPS = true + config.Opts.HTTPS = true } protocol := "HTTP" - if m.cfg.IsHTTPS { + if config.Opts.HTTPS { protocol = "HTTPS" } logger.Debug("[%s] %s %s %s", protocol, clientIP, r.Method, r.RequestURI) - if m.cfg.IsHTTPS && m.cfg.HasHSTS() { + if config.Opts.HTTPS && config.Opts.HasHSTS() { w.Header().Set("Strict-Transport-Security", "max-age=31536000") } diff --git a/service/scheduler/scheduler.go b/service/scheduler/scheduler.go index 184bb49e..1d513568 100644 --- a/service/scheduler/scheduler.go +++ b/service/scheduler/scheduler.go @@ -14,10 +14,10 @@ import ( ) // Serve starts the internal scheduler. -func Serve(cfg *config.Config, store *storage.Storage, pool *worker.Pool) { +func Serve(store *storage.Storage, pool *worker.Pool) { logger.Info(`Starting scheduler...`) - go feedScheduler(store, pool, cfg.PollingFrequency(), cfg.BatchSize()) - go cleanupScheduler(store, cfg.CleanupFrequency(), cfg.ArchiveReadDays()) + go feedScheduler(store, pool, config.Opts.PollingFrequency(), config.Opts.BatchSize()) + go cleanupScheduler(store, config.Opts.CleanupFrequency(), config.Opts.ArchiveReadDays()) } func feedScheduler(store *storage.Storage, pool *worker.Pool, frequency, batchSize int) { diff --git a/template/engine.go b/template/engine.go index e4cdc96d..c7f75660 100644 --- a/template/engine.go +++ b/template/engine.go @@ -9,7 +9,6 @@ import ( "html/template" "time" - "miniflux.app/config" "miniflux.app/errors" "miniflux.app/locale" "miniflux.app/logger" @@ -78,10 +77,10 @@ func (e *Engine) Render(name, language string, data interface{}) []byte { } // NewEngine returns a new template engine. -func NewEngine(cfg *config.Config, router *mux.Router) *Engine { +func NewEngine(router *mux.Router) *Engine { tpl := &Engine{ templates: make(map[string]*template.Template), - funcMap: newFuncMap(cfg, router), + funcMap: &funcMap{router}, } tpl.parseAll() diff --git a/template/functions.go b/template/functions.go index d99f2379..0f5b180c 100644 --- a/template/functions.go +++ b/template/functions.go @@ -7,8 +7,8 @@ package template // import "miniflux.app/template" import ( "encoding/base64" "fmt" - "math" "html/template" + "math" "net/mail" "strings" "time" @@ -20,12 +20,11 @@ import ( "miniflux.app/timezone" "miniflux.app/url" - "github.com/gorilla/mux" "github.com/PuerkitoBio/goquery" + "github.com/gorilla/mux" ) type funcMap struct { - cfg *config.Config router *mux.Router } @@ -37,13 +36,13 @@ func (f *funcMap) Map() template.FuncMap { "truncate": truncate, "isEmail": isEmail, "baseURL": func() string { - return f.cfg.BaseURL() + return config.Opts.BaseURL() }, "rootURL": func() string { - return f.cfg.RootURL() + return config.Opts.RootURL() }, "hasOAuth2Provider": func(provider string) bool { - return f.cfg.OAuth2Provider() == provider + return config.Opts.OAuth2Provider() == provider }, "route": func(name string, args ...interface{}) string { return route.Path(f.router, name, args...) @@ -52,10 +51,10 @@ func (f *funcMap) Map() template.FuncMap { return template.HTML(str) }, "proxyFilter": func(data string) string { - return imageProxyFilter(f.router, f.cfg, data) + return imageProxyFilter(f.router, data) }, "proxyURL": func(link string) string { - proxyImages := f.cfg.ProxyImages() + proxyImages := config.Opts.ProxyImages() if proxyImages == "all" || (proxyImages != "none" && !url.IsHTTPS(link)) { return proxify(f.router, link) @@ -92,10 +91,6 @@ func (f *funcMap) Map() template.FuncMap { } } -func newFuncMap(cfg *config.Config, router *mux.Router) *funcMap { - return &funcMap{cfg, router} -} - func dict(values ...interface{}) (map[string]interface{}, error) { if len(values)%2 != 0 { return nil, fmt.Errorf("dict expects an even number of arguments") @@ -178,8 +173,8 @@ func elapsedTime(printer *locale.Printer, tz string, t time.Time) string { } } -func imageProxyFilter(router *mux.Router, cfg *config.Config, data string) string { - proxyImages := cfg.ProxyImages() +func imageProxyFilter(router *mux.Router, data string) string { + proxyImages := config.Opts.ProxyImages() if proxyImages == "none" { return data } diff --git a/template/functions_test.go b/template/functions_test.go index a816eb77..ff563b27 100644 --- a/template/functions_test.go +++ b/template/functions_test.go @@ -134,13 +134,13 @@ func TestElapsedTime(t *testing.T) { func TestProxyFilterWithHttpDefault(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "http-only") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `
` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := `` if expected != output { @@ -151,13 +151,13 @@ func TestProxyFilterWithHttpDefault(t *testing.T) { func TestProxyFilterWithHttpsDefault(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "http-only") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := `` if expected != output { @@ -168,13 +168,13 @@ func TestProxyFilterWithHttpsDefault(t *testing.T) { func TestProxyFilterWithHttpNever(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "none") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := input if expected != output { @@ -185,13 +185,13 @@ func TestProxyFilterWithHttpNever(t *testing.T) { func TestProxyFilterWithHttpsNever(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "none") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := input if expected != output { @@ -202,13 +202,13 @@ func TestProxyFilterWithHttpsNever(t *testing.T) { func TestProxyFilterWithHttpAlways(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := `` if expected != output { @@ -219,13 +219,13 @@ func TestProxyFilterWithHttpAlways(t *testing.T) { func TestProxyFilterWithHttpsAlways(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "all") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := `` if expected != output { @@ -236,13 +236,13 @@ func TestProxyFilterWithHttpsAlways(t *testing.T) { func TestProxyFilterWithHttpInvalid(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "invalid") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := `` if expected != output { @@ -253,13 +253,13 @@ func TestProxyFilterWithHttpInvalid(t *testing.T) { func TestProxyFilterWithHttpsInvalid(t *testing.T) { os.Clearenv() os.Setenv("PROXY_IMAGES", "invalid") - c := config.NewConfig() + config.ParseConfig() r := mux.NewRouter() r.HandleFunc("/proxy/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy") input := `` - output := imageProxyFilter(r, c, input) + output := imageProxyFilter(r, input) expected := `` if expected != output { diff --git a/ui/entry_save.go b/ui/entry_save.go index 86d5b4d9..43d25038 100644 --- a/ui/entry_save.go +++ b/ui/entry_save.go @@ -37,7 +37,7 @@ func (h *handler) saveEntry(w http.ResponseWriter, r *http.Request) { } go func() { - integration.SendEntry(h.cfg, entry, settings) + integration.SendEntry(entry, settings) }() json.Created(w, r, map[string]string{"message": "saved"}) diff --git a/ui/handler.go b/ui/handler.go index 2bccf616..d4d48893 100644 --- a/ui/handler.go +++ b/ui/handler.go @@ -2,10 +2,9 @@ // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. -package ui // import "miniflux.app/ui" +package ui // import "miniflux.app/ui" import ( - "miniflux.app/config" "miniflux.app/reader/feed" "miniflux.app/storage" "miniflux.app/template" @@ -16,7 +15,6 @@ import ( type handler struct { router *mux.Router - cfg *config.Config store *storage.Storage tpl *template.Engine pool *worker.Pool diff --git a/ui/integration_pocket.go b/ui/integration_pocket.go index 8f8c680c..3e771878 100644 --- a/ui/integration_pocket.go +++ b/ui/integration_pocket.go @@ -2,13 +2,14 @@ // Use of this source code is governed by the Apache 2.0 // license that can be found in the LICENSE file. -package ui // import "miniflux.app/ui" +package ui // import "miniflux.app/ui" import ( "net/http" - "miniflux.app/http/response/html" + "miniflux.app/config" "miniflux.app/http/request" + "miniflux.app/http/response/html" "miniflux.app/http/route" "miniflux.app/integration/pocket" "miniflux.app/locale" @@ -31,8 +32,8 @@ func (h *handler) pocketAuthorize(w http.ResponseWriter, r *http.Request) { } sess := session.New(h.store, request.SessionID(r)) - connector := pocket.NewConnector(h.cfg.PocketConsumerKey(integration.PocketConsumerKey)) - redirectURL := h.cfg.BaseURL() + route.Path(h.router, "pocketCallback") + connector := pocket.NewConnector(config.Opts.PocketConsumerKey(integration.PocketConsumerKey)) + redirectURL := config.Opts.BaseURL() + route.Path(h.router, "pocketCallback") requestToken, err := connector.RequestToken(redirectURL) if err != nil { logger.Error("[Pocket:Authorize] %v", err) @@ -61,7 +62,7 @@ func (h *handler) pocketCallback(w http.ResponseWriter, r *http.Request) { return } - connector := pocket.NewConnector(h.cfg.PocketConsumerKey(integration.PocketConsumerKey)) + connector := pocket.NewConnector(config.Opts.PocketConsumerKey(integration.PocketConsumerKey)) accessToken, err := connector.AccessToken(request.PocketRequestToken(r)) if err != nil { logger.Error("[Pocket:Callback] %v", err) diff --git a/ui/integration_show.go b/ui/integration_show.go index c7f27406..a43bb076 100644 --- a/ui/integration_show.go +++ b/ui/integration_show.go @@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui" import ( "net/http" + "miniflux.app/config" "miniflux.app/http/request" "miniflux.app/http/response/html" "miniflux.app/ui/form" @@ -59,7 +60,7 @@ func (h *handler) showIntegrationPage(w http.ResponseWriter, r *http.Request) { view.Set("user", user) view.Set("countUnread", h.store.CountUnreadEntries(user.ID)) view.Set("countErrorFeeds", h.store.CountErrorFeeds(user.ID)) - view.Set("hasPocketConsumerKeyConfigured", h.cfg.PocketConsumerKey("") != "") + view.Set("hasPocketConsumerKeyConfigured", config.Opts.PocketConsumerKey("") != "") html.OK(w, r, view.Render("integrations")) } diff --git a/ui/login_check.go b/ui/login_check.go index 46229a45..99c6a702 100644 --- a/ui/login_check.go +++ b/ui/login_check.go @@ -3,6 +3,7 @@ package ui // import "miniflux.app/ui" import ( "net/http" + "miniflux.app/config" "miniflux.app/http/cookie" "miniflux.app/http/request" "miniflux.app/http/response/html" @@ -55,8 +56,8 @@ func (h *handler) checkLogin(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, cookie.New( cookie.CookieUserSessionID, sessionToken, - h.cfg.IsHTTPS, - h.cfg.BasePath(), + config.Opts.HTTPS, + config.Opts.BasePath(), )) html.Redirect(w, r, route.Path(h.router, "unread")) diff --git a/ui/logout.go b/ui/logout.go index 7c63c324..479426b5 100644 --- a/ui/logout.go +++ b/ui/logout.go @@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui" import ( "net/http" + "miniflux.app/config" "miniflux.app/http/cookie" "miniflux.app/http/request" "miniflux.app/http/response/html" @@ -32,8 +33,8 @@ func (h *handler) logout(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, cookie.Expired( cookie.CookieUserSessionID, - h.cfg.IsHTTPS, - h.cfg.BasePath(), + config.Opts.HTTPS, + config.Opts.BasePath(), )) html.Redirect(w, r, route.Path(h.router, "login")) diff --git a/ui/middleware.go b/ui/middleware.go index f53ac4e3..5f6dacd2 100644 --- a/ui/middleware.go +++ b/ui/middleware.go @@ -14,21 +14,20 @@ import ( "miniflux.app/http/request" "miniflux.app/http/response/html" "miniflux.app/http/route" - "miniflux.app/storage" "miniflux.app/logger" "miniflux.app/model" + "miniflux.app/storage" "github.com/gorilla/mux" ) type middleware struct { router *mux.Router - cfg *config.Config - store *storage.Storage + store *storage.Storage } -func newMiddleware(router *mux.Router, cfg *config.Config, store *storage.Storage) *middleware { - return &middleware{router, cfg, store} +func newMiddleware(router *mux.Router, store *storage.Storage) *middleware { + return &middleware{router, store} } func (m *middleware) handleUserSession(next http.Handler) http.Handler { @@ -61,7 +60,7 @@ func (m *middleware) handleAppSession(next http.Handler) http.Handler { session := m.getAppSessionValueFromCookie(r) if session == nil { - if (request.IsAuthenticated(r)) { + if request.IsAuthenticated(r) { userID := request.UserID(r) logger.Debug("[UI:AppSession] Cookie expired but user #%d is logged: creating a new session", userID) session, err = m.store.CreateAppSessionWithUserPrefs(userID) @@ -78,7 +77,7 @@ func (m *middleware) handleAppSession(next http.Handler) http.Handler { } } - http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, m.cfg.IsHTTPS, m.cfg.BasePath())) + http.SetCookie(w, cookie.New(cookie.CookieAppSessionID, session.ID, config.Opts.HTTPS, config.Opts.BasePath())) } else { logger.Debug("[UI:AppSession] %s", session) } diff --git a/ui/oauth2.go b/ui/oauth2.go index c5d594fc..256137d6 100644 --- a/ui/oauth2.go +++ b/ui/oauth2.go @@ -9,10 +9,10 @@ import ( "miniflux.app/oauth2" ) -func getOAuth2Manager(cfg *config.Config) *oauth2.Manager { +func getOAuth2Manager() *oauth2.Manager { return oauth2.NewManager( - cfg.OAuth2ClientID(), - cfg.OAuth2ClientSecret(), - cfg.OAuth2RedirectURL(), + config.Opts.OAuth2ClientID(), + config.Opts.OAuth2ClientSecret(), + config.Opts.OAuth2RedirectURL(), ) } diff --git a/ui/oauth2_callback.go b/ui/oauth2_callback.go index bd7c999c..6dce3d94 100644 --- a/ui/oauth2_callback.go +++ b/ui/oauth2_callback.go @@ -7,6 +7,7 @@ package ui // import "miniflux.app/ui" import ( "net/http" + "miniflux.app/config" "miniflux.app/http/cookie" "miniflux.app/http/request" "miniflux.app/http/response/html" @@ -43,7 +44,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) { return } - authProvider, err := getOAuth2Manager(h.cfg).Provider(provider) + authProvider, err := getOAuth2Manager().Provider(provider) if err != nil { logger.Error("[OAuth2] %v", err) html.Redirect(w, r, route.Path(h.router, "login")) @@ -90,7 +91,7 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) { } if user == nil { - if !h.cfg.IsOAuth2UserCreationAllowed() { + if !config.Opts.IsOAuth2UserCreationAllowed() { html.Forbidden(w, r) return } @@ -121,8 +122,8 @@ func (h *handler) oauth2Callback(w http.ResponseWriter, r *http.Request) { http.SetCookie(w, cookie.New( cookie.CookieUserSessionID, sessionToken, - h.cfg.IsHTTPS, - h.cfg.BasePath(), + config.Opts.HTTPS, + config.Opts.BasePath(), )) html.Redirect(w, r, route.Path(h.router, "unread")) diff --git a/ui/oauth2_redirect.go b/ui/oauth2_redirect.go index 85116cff..3a4a54f1 100644 --- a/ui/oauth2_redirect.go +++ b/ui/oauth2_redirect.go @@ -24,7 +24,7 @@ func (h *handler) oauth2Redirect(w http.ResponseWriter, r *http.Request) { return } - authProvider, err := getOAuth2Manager(h.cfg).Provider(provider) + authProvider, err := getOAuth2Manager().Provider(provider) if err != nil { logger.Error("[OAuth2] %v", err) html.Redirect(w, r, route.Path(h.router, "login")) diff --git a/ui/oauth2_unlink.go b/ui/oauth2_unlink.go index 3283f897..84b009bb 100644 --- a/ui/oauth2_unlink.go +++ b/ui/oauth2_unlink.go @@ -24,7 +24,7 @@ func (h *handler) oauth2Unlink(w http.ResponseWriter, r *http.Request) { return } - authProvider, err := getOAuth2Manager(h.cfg).Provider(provider) + authProvider, err := getOAuth2Manager().Provider(provider) if err != nil { logger.Error("[OAuth2] %v", err) html.Redirect(w, r, route.Path(h.router, "settings")) diff --git a/ui/ui.go b/ui/ui.go index 71bbe699..47383d20 100644 --- a/ui/ui.go +++ b/ui/ui.go @@ -7,7 +7,6 @@ package ui // import "miniflux.app/ui" import ( "net/http" - "miniflux.app/config" "miniflux.app/reader/feed" "miniflux.app/storage" "miniflux.app/template" @@ -17,9 +16,9 @@ import ( ) // Serve declares all routes for the user interface. -func Serve(router *mux.Router, cfg *config.Config, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) { - middleware := newMiddleware(router, cfg, store) - handler := &handler{router, cfg, store, template.NewEngine(cfg, router), pool, feedHandler} +func Serve(router *mux.Router, store *storage.Storage, pool *worker.Pool, feedHandler *feed.Handler) { + middleware := newMiddleware(router, store) + handler := &handler{router, store, template.NewEngine(router), pool, feedHandler} uiRouter := router.NewRoute().Subrouter() uiRouter.Use(middleware.handleUserSession)