improve: Add type safety, middleware, and comprehensive handler tests
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
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user