Commit 648a15c

bryfry <bryon@fryer.io>
2025-07-24 09:25:07
Task 1.11: Implement configuration management system
- Create config struct with port, env, staticDir, htmlDir fields - Add command-line flag parsing with sensible defaults: * -port (default: 4000): HTTP server port * -env (default: development): Environment mode * -static-dir (default: ./ui/static): Static assets path * -html-dir (default: ./ui/html): HTML templates path - Add environment variable overrides: PORT, ENV, STATIC_DIR, HTML_DIR - Update application struct to include config - Replace all hardcoded paths with configurable alternatives - Update handlers to use config.htmlDir for template paths - Update main.go to use config.staticDir and config.port - Add informative startup logging with port and environment - Update project_plan.md status to Completed Configuration examples: - Default: go run cmd/web/*.go - Custom port: go run cmd/web/*.go -port=8080 - Environment: ENV=production go run cmd/web/*.go - Help: go run cmd/web/*.go -help See: docs/todo/task_1.11.md
1 parent ff0a3a9
Changed files (3)
cmd/web/handlers.go
@@ -14,9 +14,18 @@ type PageData struct {
 	Content     interface{}
 }
 
+// config holds all the configuration settings for the application
+type config struct {
+	port      int
+	env       string
+	staticDir string
+	htmlDir   string
+}
+
 // application holds the application-wide dependencies and configuration
 // This demonstrates a custom handler type that can hold state and dependencies
 type application struct {
+	config      config
 	logger      *log.Logger
 	serviceName string
 }
@@ -34,10 +43,10 @@ type TemplateHandler struct {
 func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	w.Header().Add("Server", th.app.serviceName)
 
-	// Parse templates with inheritance
+	// Parse templates with inheritance using configurable paths
 	ts, err := template.ParseFiles(
-		"./ui/html/layouts/base.tmpl",
-		"./ui/html/pages/"+th.templateName,
+		th.app.config.htmlDir+"/layouts/base.tmpl",
+		th.app.config.htmlDir+"/pages/"+th.templateName,
 	)
 	if err != nil {
 		th.app.logger.Println(err.Error())
@@ -66,10 +75,10 @@ func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 func (app *application) home(w http.ResponseWriter, r *http.Request) {
 	w.Header().Add("Server", app.serviceName)
 
-	// Parse templates with inheritance
+	// Parse templates with inheritance using configurable paths
 	ts, err := template.ParseFiles(
-		"./ui/html/layouts/base.tmpl",
-		"./ui/html/pages/home.tmpl",
+		app.config.htmlDir+"/layouts/base.tmpl",
+		app.config.htmlDir+"/pages/home.tmpl",
 	)
 	if err != nil {
 		app.logger.Println(err.Error())
@@ -100,10 +109,10 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
 func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
 	w.Header().Add("Server", app.serviceName)
 
-	// Parse templates with inheritance
+	// Parse templates with inheritance using configurable paths
 	ts, err := template.ParseFiles(
-		"./ui/html/layouts/base.tmpl",
-		"./ui/html/pages/complete.tmpl",
+		app.config.htmlDir+"/layouts/base.tmpl",
+		app.config.htmlDir+"/pages/complete.tmpl",
 	)
 	if err != nil {
 		app.logger.Println(err.Error())
cmd/web/main.go
@@ -1,14 +1,44 @@
 package main
 
 import (
+	"flag"
 	"log"
 	"net/http"
+	"os"
+	"strconv"
 )
 
 func main() {
+	// Declare an instance of the config struct to hold configuration settings
+	var cfg config
+
+	// Parse command-line flags with sensible defaults
+	flag.IntVar(&cfg.port, "port", 4000, "HTTP server port")
+	flag.StringVar(&cfg.env, "env", "development", "Environment (development|staging|production)")
+	flag.StringVar(&cfg.staticDir, "static-dir", "./ui/static", "Path to static assets")
+	flag.StringVar(&cfg.htmlDir, "html-dir", "./ui/html", "Path to HTML templates")
+	flag.Parse()
+
+	// Check for environment variable overrides
+	if envPort := os.Getenv("PORT"); envPort != "" {
+		if port, err := strconv.Atoi(envPort); err == nil {
+			cfg.port = port
+		}
+	}
+	if envEnv := os.Getenv("ENV"); envEnv != "" {
+		cfg.env = envEnv
+	}
+	if envStaticDir := os.Getenv("STATIC_DIR"); envStaticDir != "" {
+		cfg.staticDir = envStaticDir
+	}
+	if envHtmlDir := os.Getenv("HTML_DIR"); envHtmlDir != "" {
+		cfg.htmlDir = envHtmlDir
+	}
+
 	// Create an instance of the application struct, containing the application-wide
 	// dependencies and configuration settings.
 	app := &application{
+		config:      cfg,
 		logger:      log.New(log.Writer(), "", log.LstdFlags),
 		serviceName: "buylater.email",
 	}
@@ -17,10 +47,10 @@ func main() {
 	// URL patterns and their corresponding handlers.
 	mux := http.NewServeMux()
 
-	// Create a file server which serves files out of the "./ui/static" directory.
+	// Create a file server which serves files out of the configured static directory.
 	// Note that the path given to the http.Dir function is relative to the project
 	// directory root.
-	fileServer := http.FileServer(http.Dir("./ui/static/"))
+	fileServer := http.FileServer(http.Dir(app.config.staticDir))
 
 	// Use the mux.Handle() function to register the file server as the handler for
 	// all URL paths that start with "/static/". For matching paths, we strip the
@@ -55,10 +85,10 @@ func main() {
 	})
 
 	// Print a log message to indicate the server is starting.
-	app.logger.Printf("Starting server on http://localhost:4000")
+	app.logger.Printf("Starting server on http://localhost:%d in %s mode", app.config.port, app.config.env)
 
-	// Start the web server on port 4000. If ListenAndServe returns an error
+	// Start the web server on the configured port. If ListenAndServe returns an error
 	// we use log.Fatal() to log the error and terminate the program.
-	err := http.ListenAndServe(":4000", mux)
+	err := http.ListenAndServe(":"+strconv.Itoa(app.config.port), mux)
 	log.Fatal(err)
 }
docs/project_plan.md
@@ -18,7 +18,7 @@ Phase 1 lays the groundwork by methodically working through the Let's Go book, c
 | 1.8  | HTML Templating and Inheritance         | Completed | Medium | 2.8     | [task_1.8.md](todo/task_1.8.md) |
 | 1.9  | Serving Static Files                    | Completed | Small  | 2.9     | [task_1.9.md](todo/task_1.9.md) |
 | 1.10 | The HTTP Handler Interface              | Completed | Medium | 2.10    | [task_1.10.md](todo/task_1.10.md) |
-| 1.11 | Managing Configuration Settings         | Pending   | Medium | 3.1     | [task_1.11.md](todo/task_1.11.md) |
+| 1.11 | Managing Configuration Settings         | Completed | Medium | 3.1     | [task_1.11.md](todo/task_1.11.md) |
 | 1.12 | Structured Logging                      | Pending   | Medium | 3.2     | [task_1.12.md](todo/task_1.12.md) |
 | 1.13 | Dependency Injection                    | Pending   | Medium | 3.3     | [task_1.13.md](todo/task_1.13.md) |
 | 1.14 | Centralized Error Handling             | Pending   | Medium | 3.4     | [task_1.14.md](todo/task_1.14.md) |