diff --git a/.gitignore b/.gitignore index d6828d9..26f83da 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ config.*.json archivers-api gin-bin +*.env \ No newline at end of file diff --git a/auth.go b/auth.go index 123dd76..f04c6c6 100644 --- a/auth.go +++ b/auth.go @@ -9,6 +9,7 @@ import ( "strings" ) +// Proxied User model. The real user model is in github.com/archivers-space/identity/user.go type User struct { Id string `json:"id" sql:"id"` Created int64 `json:"created" sql:"created"` diff --git a/config.go b/config.go index d1db002..638b1cb 100644 --- a/config.go +++ b/config.go @@ -1,12 +1,10 @@ package main import ( - "encoding/json" "fmt" - "io/ioutil" + conf "github.com/archivers-space/config" "os" "path/filepath" - "strings" ) // server modes @@ -18,13 +16,12 @@ const ( // config holds all configuration for the server. It pulls from three places (in order): // 1. environment variables -// 2. config.[server_mode].json <- eg: config.test.json -// 3. config.json +// 2. .[MODE].env OR .env // -// env variables win, but can only set config who's json is ALL_CAPS -// it's totally fine to not have, say, config.develop.json defined, and just -// rely on a base config.json. But if you're in production mode & config.production.json -// exists, that will be read *instead* of config.json. +// globally-set env variables win. +// it's totally fine to not have, say, .env.develop defined, and just +// rely on a base ".env" file. But if you're in production mode & ".env.production" +// exists, that will be read *instead* of .env // // configuration is read at startup and cannot be alterd without restarting the server. type config struct { @@ -69,23 +66,13 @@ type config struct { func initConfig(mode string) (cfg *config, err error) { cfg = &config{} - if err := loadConfigFile(mode, cfg); err != nil { - return cfg, err + if path := configFilePath(mode, cfg); path != "" { + logger.Printf("loading config file: %s", filepath.Base(path)) + conf.Load(cfg, path) + } else { + conf.Load(cfg) } - // override config settings with env settings, passing in the current configuration - // as the default. This has the effect of leaving the config.json value unchanged - // if the env variable is empty - cfg.Gopath = readEnvString("GOPATH", cfg.Gopath) - cfg.Port = readEnvString("PORT", cfg.Port) - cfg.UrlRoot = readEnvString("URL_ROOT", cfg.UrlRoot) - cfg.PublicKey = readEnvString("PUBLIC_KEY", cfg.PublicKey) - cfg.TLS = readEnvBool("TLS", cfg.TLS) - cfg.AllowedOrigins = readEnvStringSlice("ALLOWED_ORIGINS", cfg.AllowedOrigins) - cfg.CertbotResponse = readEnvString("CERTBOT_RESPONSE", cfg.CertbotResponse) - cfg.AnalyticsToken = readEnvString("ANALYTICS_TOKEN", cfg.AnalyticsToken) - cfg.PostgresDbUrl = readEnvString("POSTGRES_DB_URL", cfg.PostgresDbUrl) - // make sure port is set if cfg.Port == "" { cfg.Port = "8080" @@ -103,30 +90,6 @@ func packagePath(path string) string { return filepath.Join(os.Getenv("GOPATH"), "src/github.com/archivers-space/archivers-api", path) } -// readEnvString reads key from the environment, returns def if empty -func readEnvString(key, def string) string { - if env := os.Getenv(key); env != "" { - return env - } - return def -} - -// readEnvBool read key form the env, converting to a boolean value. returns def if empty -func readEnvBool(key string, def bool) bool { - if env := os.Getenv(key); env != "" { - return env == "true" || env == "TRUE" || env == "t" - } - return def -} - -// readEnvString reads a slice of strings from key environment var, returns def if empty -func readEnvStringSlice(key string, def []string) []string { - if env := os.Getenv(key); env != "" { - return strings.Split(env, ",") - } - return def -} - // requireConfigStrings panics if any of the passed in values aren't set func requireConfigStrings(values map[string]string) error { for key, value := range values { @@ -138,33 +101,17 @@ func requireConfigStrings(values map[string]string) error { return nil } -// checks for config.[mode].json file to read configuration from if the file exists -// defaults to config.json, silently fails if no configuration file is present. -func loadConfigFile(mode string, cfg *config) (err error) { - var data []byte - - fileName := packagePath(fmt.Sprintf("config.%s.json", mode)) +// checks for .[mode].env file to read configuration from if the file exists +// defaults to .env, returns "" if no file is present +func configFilePath(mode string, cfg *config) string { + fileName := packagePath(fmt.Sprintf(".%s.env", mode)) if !fileExists(fileName) { - fileName = packagePath("config.json") + fileName = packagePath(".env") if !fileExists(fileName) { - return nil + return "" } } - - logger.Printf("reading config file: %s", fileName) - data, err = ioutil.ReadFile(fileName) - if err != nil { - err = fmt.Errorf("error reading %s: %s", fileName, err) - return - } - - // unmarshal ("decode") config data into a config struct - if err = json.Unmarshal(data, cfg); err != nil { - err = fmt.Errorf("error parsing %s: %s", fileName, err) - return - } - - return + return fileName } // Does this file exist? diff --git a/config.json b/config.json deleted file mode 100644 index 823928f..0000000 --- a/config.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "URL_ROOT" : "", - "TLS" : false, - "PORT" : "", - "POSTGRES_DB_URL" : "", - "PUBLIC_KEY" : "", - "proxyForceHttps" : true, - "identityServerUrl" : "https://ident.archivers.space", - "ANALYTICS_TOKEN" : "FwUGnmKzryJpDpApVzdQy9rmwwWSiK1M" -} \ No newline at end of file diff --git a/server.go b/server.go index 1303d6a..f38bc6e 100644 --- a/server.go +++ b/server.go @@ -18,6 +18,8 @@ var ( appDB *sql.DB ) +// NewServerRoutes returns a Muxer that has all API routes. +// This makes for easy testing using httptest, see server_test.go func NewServerRoutes() *http.ServeMux { m := http.NewServeMux() @@ -27,31 +29,41 @@ func NewServerRoutes() *http.ServeMux { // m.Handle("/v0/users", middleware(UserHandler)) // m.Handle("/v0/users/", middleware(UsersHandler)) + m.Handle("/v0/primers", middleware(PrimersHandler)) m.Handle("/v0/primers/", middleware(PrimerHandler)) + m.Handle("/v0/sources", middleware(SourcesHandler)) m.Handle("/v0/sources/", middleware(SourceHandler)) + m.Handle("/v0/urls", middleware(UrlsHandler)) m.Handle("/v0/urls/", middleware(UrlHandler)) + m.Handle("/v0/uncrawlables", middleware(UncrawlablesHandler)) m.Handle("/v0/uncrawlables/", middleware(UncrawlableHandler)) // m.Handle("/v0/links", middleware(UrlHandler)) // m.Handle("/v0/links/", middleware(UrlsHandler)) + // m.Handle("/v0/snapshots", middleware()) // m.Handle("/v0/snapshots/", middleware()) + // m.Handle("/v0/content", middleware()) // m.Handle("/v0/content/", middleware()) + // m.Handle("/v0/metadata", middleware()) // m.Handle("/v0/metadata/", middleware()) + // m.Handle("/v0/consensus", middleware()) // m.Handle("/v0/consensus/", middleware()) + // m.Handle("/v0/collections", middleware()) // m.Handle("/v0/collections/", middleware()) return m } +// main app entry point func main() { var err error cfg, err = initConfig(os.Getenv("GOLANG_ENV")) @@ -60,10 +72,13 @@ func main() { panic(fmt.Errorf("server configuration error: %s", err.Error())) } + // TODO - run this in a goroutine & support reporting "oh snap no DB" + // while waiting for a connection connectToAppDb() + // base server s := &http.Server{} - // connect mux to server + // connect mux routes to server s.Handler = NewServerRoutes() // print notable config settings diff --git a/transports.go b/transports.go index e29acf5..3f9991a 100644 --- a/transports.go +++ b/transports.go @@ -10,6 +10,10 @@ import ( "golang.org/x/crypto/acme/autocert" ) +// StartServer interprets info from config to start the server +// if config.TLS == true it'll spin up an https server using LetsEncrypt +// that should work just fine on the raw internet (ie not behind a proxy like nginx etc) +// it'll also redirect http traffic to it's https route counterpart if port 80 is open func StartServer(c *config, s *http.Server) error { s.Addr = fmt.Sprintf(fmt.Sprintf(":%s", c.Port))