8a709c6863
Five complementary improvements to handler layer: 1. Fix Pre-Commit Hook - Remove broken Perl-style regex (unsupported by Go) - Use -short flag to exclude integration tests - Tests now run successfully in pre-commit 2. Extract Duplicate Logic - Remove 100+ lines of duplicate data preparation - Both Home() and CVContent() now use prepareTemplateData() - Reduce cv_pages.go from 290 to 120 lines (58% reduction) 3. Request/Response Types - Create internal/handlers/types.go with structured types - PDFExportRequest, LanguageRequest, PreferenceToggleRequest - Type-safe parameter parsing with centralized validation - Refactor ExportPDF to use typed requests 4. Middleware Extraction - Create internal/middleware/preferences.go - PreferencesMiddleware reads cookies once, stores in context - Automatic migration of old preference values - Ready for integration in routes 5. Handler Tests - Add internal/handlers/cv_pages_test.go (190 lines, 15+ cases) - Add internal/handlers/cv_htmx_test.go (325 lines, 20+ cases) - Test language validation, toggles, cookies, methods - Increase handler test coverage significantly Testing: - All unit tests pass (35+ new test cases) - Pre-commit hook working - Build succeeds - No breaking changes Benefits: - Type safety: Compile-time parameter validation - Code quality: 170 lines of duplication eliminated - Testing: 100% increase in test files - Architecture: Clean middleware pattern - Developer experience: Self-documenting request types Documentation: - Create _go-learning/refactorings/004-handler-improvements.md - Document all five improvements with examples - Include metrics, testing strategy, and future improvements
91 lines
2.5 KiB
Go
91 lines
2.5 KiB
Go
package middleware
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
)
|
|
|
|
// contextKey is a private type for context keys to avoid collisions
|
|
type contextKey string
|
|
|
|
const (
|
|
// PreferencesKey is the context key for user preferences
|
|
PreferencesKey contextKey = "preferences"
|
|
)
|
|
|
|
// Preferences holds user preference values from cookies
|
|
type Preferences struct {
|
|
CVLength string // "short" or "long"
|
|
CVIcons string // "show" or "hide"
|
|
CVLanguage string // "en" or "es"
|
|
CVTheme string // "default" or "clean"
|
|
ColorTheme string // "light" or "dark"
|
|
}
|
|
|
|
// PreferencesMiddleware reads user preferences from cookies and stores them in context
|
|
// This eliminates the need for handlers to manually read cookies
|
|
func PreferencesMiddleware(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
prefs := &Preferences{
|
|
CVLength: getPreferenceCookie(r, "cv-length", "short"),
|
|
CVIcons: getPreferenceCookie(r, "cv-icons", "show"),
|
|
CVLanguage: getPreferenceCookie(r, "cv-language", "en"),
|
|
CVTheme: getPreferenceCookie(r, "cv-theme", "default"),
|
|
ColorTheme: getPreferenceCookie(r, "color-theme", "light"),
|
|
}
|
|
|
|
// Migrate old preference values (one-time auto-migration)
|
|
if prefs.CVLength == "extended" {
|
|
prefs.CVLength = "long"
|
|
}
|
|
switch prefs.CVIcons {
|
|
case "true":
|
|
prefs.CVIcons = "show"
|
|
case "false":
|
|
prefs.CVIcons = "hide"
|
|
}
|
|
|
|
// Store preferences in context
|
|
ctx := context.WithValue(r.Context(), PreferencesKey, prefs)
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
})
|
|
}
|
|
|
|
// GetPreferences retrieves preferences from request context
|
|
func GetPreferences(r *http.Request) *Preferences {
|
|
prefs, ok := r.Context().Value(PreferencesKey).(*Preferences)
|
|
if !ok {
|
|
// Return default preferences if not found
|
|
return &Preferences{
|
|
CVLength: "short",
|
|
CVIcons: "show",
|
|
CVLanguage: "en",
|
|
CVTheme: "default",
|
|
ColorTheme: "light",
|
|
}
|
|
}
|
|
return prefs
|
|
}
|
|
|
|
// SetPreferenceCookie sets a preference cookie (1 year expiry)
|
|
func SetPreferenceCookie(w http.ResponseWriter, name string, value string) {
|
|
http.SetCookie(w, &http.Cookie{
|
|
Name: name,
|
|
Value: value,
|
|
Path: "/",
|
|
MaxAge: 365 * 24 * 60 * 60, // 1 year
|
|
HttpOnly: true,
|
|
SameSite: http.SameSiteStrictMode,
|
|
Secure: false, // Set to true in production with HTTPS
|
|
})
|
|
}
|
|
|
|
// getPreferenceCookie gets a preference cookie value, returns default if not found
|
|
func getPreferenceCookie(r *http.Request, name string, defaultValue string) string {
|
|
cookie, err := r.Cookie(name)
|
|
if err != nil {
|
|
return defaultValue
|
|
}
|
|
return cookie.Value
|
|
}
|