Commit 3998ae5

bryfry <bryon@fryer.io>
2025-07-24 09:55:35
Task 1.14: Implement centralized error handling system
- Create centralized error handling helper methods (serverError, clientError, notFound) - Add proper error logging with stack traces and context - Create user-friendly branded error page template with responsive design - Update all handlers to use centralized error handling instead of http.Error - Add error categorization for client vs server errors - Implement consistent HTTP error response format throughout application See: docs/todo/task_1.14.md
1 parent 02735b0
Changed files (4)
cmd/web/handlers.go
@@ -74,8 +74,7 @@ func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 		th.app.config.htmlDir+"/pages/"+th.templateName,
 	)
 	if err != nil {
-		th.app.logger.error.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
+		th.app.serverError(w, err)
 		return
 	}
 
@@ -89,8 +88,7 @@ func (th *TemplateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		th.app.logger.error.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
+		th.app.serverError(w, err)
 	}
 }
 
@@ -106,8 +104,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
 		app.config.htmlDir+"/pages/home.tmpl",
 	)
 	if err != nil {
-		app.logger.error.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
+		app.serverError(w, err)
 		return
 	}
 
@@ -121,8 +118,7 @@ func (app *application) home(w http.ResponseWriter, r *http.Request) {
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		app.logger.error.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
+		app.serverError(w, err)
 	}
 }
 
@@ -140,8 +136,7 @@ func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
 		app.config.htmlDir+"/pages/complete.tmpl",
 	)
 	if err != nil {
-		app.logger.error.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
+		app.serverError(w, err)
 		return
 	}
 
@@ -155,8 +150,7 @@ func (app *application) processSubmit(w http.ResponseWriter, r *http.Request) {
 	// Execute template
 	err = ts.ExecuteTemplate(w, "base", data)
 	if err != nil {
-		app.logger.error.Println(err.Error())
-		http.Error(w, "Internal Server Error", 500)
+		app.serverError(w, err)
 	}
 }
 
@@ -166,9 +160,8 @@ func (app *application) confirmWithToken(w http.ResponseWriter, r *http.Request)
 	token := r.PathValue("token")
 
 	if !isValidToken(token) {
-		w.Header().Add("Server", app.serviceName)
 		app.logger.error.Printf("Invalid token attempted: %s", token)
-		http.NotFound(w, r)
+		app.notFound(w)
 		return
 	}
 
docs/project_plan.md
@@ -21,7 +21,7 @@ Phase 1 lays the groundwork by methodically working through the Let's Go book, c
 | 1.11 | Managing Configuration Settings         | Completed | Medium | 3.1     | [task_1.11.md](todo/task_1.11.md) |
 | 1.12 | Structured Logging                      | Completed | Medium | 3.2     | [task_1.12.md](todo/task_1.12.md) |
 | 1.13 | Dependency Injection                    | Completed | 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) |
+| 1.14 | Centralized Error Handling             | Completed | Medium | 3.4     | [task_1.14.md](todo/task_1.14.md) |
 | 1.15 | Isolating the Application Routes        | Pending   | Small  | 3.5     | [task_1.15.md](todo/task_1.15.md) |
 
 ## Status Legend
ui/html/pages/error.tmpl
@@ -0,0 +1,124 @@
+{{define "title"}}{{.Title}} - {{.ServiceName}}{{end}}
+
+{{define "main"}}
+<div class="error-page">
+    <div class="error-content">
+        <div class="error-code">{{.Content.ErrorCode}}</div>
+        <h1 class="error-title">{{.Content.ErrorTitle}}</h1>
+        <p class="error-message">{{.Content.ErrorMessage}}</p>
+        
+        <div class="error-actions">
+            <a href="/" class="cta-button">Go Home</a>
+            <a href="/submit" class="cta-button secondary">Schedule Reminder</a>
+        </div>
+        
+        <div class="error-help">
+            <p>If you continue to experience problems, please check:</p>
+            <ul>
+                <li>The URL is typed correctly</li>
+                <li>The page you're looking for still exists</li>
+                <li>Your internet connection is working</li>
+            </ul>
+        </div>
+    </div>
+</div>
+
+<style>
+.error-page {
+    text-align: center;
+    padding: 4rem 0;
+    min-height: 60vh;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.error-content {
+    max-width: 600px;
+    margin: 0 auto;
+}
+
+.error-code {
+    font-size: 8rem;
+    font-weight: bold;
+    color: #e74c3c;
+    line-height: 1;
+    margin-bottom: 1rem;
+    text-shadow: 2px 2px 4px rgba(0,0,0,0.1);
+}
+
+.error-title {
+    font-size: 2.5rem;
+    margin-bottom: 1rem;
+    color: #2c3e50;
+}
+
+.error-message {
+    font-size: 1.2rem;
+    margin-bottom: 2rem;
+    color: #7f8c8d;
+    line-height: 1.6;
+}
+
+.error-actions {
+    margin: 2rem 0;
+    display: flex;
+    gap: 1rem;
+    justify-content: center;
+    flex-wrap: wrap;
+}
+
+.error-actions .cta-button.secondary {
+    background: transparent;
+    color: #e74c3c;
+    border: 2px solid #e74c3c;
+}
+
+.error-actions .cta-button.secondary:hover {
+    background: #e74c3c;
+    color: white;
+}
+
+.error-help {
+    margin-top: 3rem;
+    padding: 2rem;
+    background: #f8f9fa;
+    border-radius: 8px;
+    text-align: left;
+}
+
+.error-help h3 {
+    margin-bottom: 1rem;
+    color: #2c3e50;
+}
+
+.error-help ul {
+    margin: 1rem 0;
+    padding-left: 1.5rem;
+}
+
+.error-help li {
+    margin-bottom: 0.5rem;
+    color: #7f8c8d;
+}
+
+@media (max-width: 768px) {
+    .error-code {
+        font-size: 6rem;
+    }
+    
+    .error-title {
+        font-size: 2rem;
+    }
+    
+    .error-actions {
+        flex-direction: column;
+        align-items: center;
+    }
+    
+    .error-actions .cta-button {
+        width: 200px;
+    }
+}
+</style>
+{{end}}
\ No newline at end of file
buylater.service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Buylater Email Service
+After=network.target
+
+[Service]
+Type=simple
+ExecStart=/home/crash/git/buylater/bin/buylater -port=3003
+WorkingDirectory=/home/crash/git/buylater
+Restart=always
+RestartSec=5
+
+[Install]
+WantedBy=default.target
\ No newline at end of file