4528e04bad
Implemented 5 additional architectural improvements: 1. Response Types (types.go) - APIResponse with Success, Data, Error, Meta fields - ErrorInfo with Code, Message, Field, Details - MetaInfo with Timestamp, Version, RequestID - SuccessResponse() and NewErrorResponse() helpers - HealthCheckResponse for health endpoint - Consistent JSON API responses 2. Validation Tags (types.go) - Added struct tags to LanguageRequest - Added struct tags to PDFExportRequest - Declarative validation rules (oneof, required) - Self-documenting validation constraints - Ready for go-playground/validator integration 3. Context Helper Functions (middleware/preferences.go) - GetLanguage(), GetCVLength(), GetCVIcons(), GetCVTheme(), GetColorTheme() - IsLongCV(), IsShortCV() boolean helpers - ShowIcons(), HideIcons() boolean helpers - IsCleanTheme(), IsDefaultTheme() boolean helpers - IsDarkMode(), IsLightMode() boolean helpers - 13 new convenience functions for cleaner code 4. Typed Errors (errors.go) - ErrorCode constants for all error types - DomainError with Code, Message, Err, StatusCode, Field - Unwrap() support for error chains - WithError() and WithField() fluent builders - InvalidLanguageError(), InvalidLengthError(), etc. - PDFGenerationError(), MethodNotAllowedError(), RateLimitError() - 13 error codes, domain-specific constructors 5. Benchmark Tests - handlers/benchmarks_test.go (11 benchmarks) - middleware/benchmarks_test.go (12 benchmarks) - Sequential benchmarks for handlers, middleware, request parsing - Parallel benchmarks for concurrent load testing - Response creation benchmarks - Helper function benchmarks Benefits: - Type Safety: Validation tags and structured types - Developer Experience: 13 context helpers reduce boilerplate - Error Handling: Domain-specific errors with codes - Performance Monitoring: 23 benchmarks for regression detection - API Consistency: Standardized response formats - Maintainability: Self-documenting validation and errors Testing: - All unit tests pass - All benchmarks working - Build succeeds - No breaking changes
161 lines
4.5 KiB
Go
161 lines
4.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
|
|
}
|
|
|
|
// ==============================================================================
|
|
// CONTEXT HELPER FUNCTIONS
|
|
// Convenience functions for accessing specific preference values
|
|
// ==============================================================================
|
|
|
|
// GetLanguage retrieves the user's language preference
|
|
func GetLanguage(r *http.Request) string {
|
|
return GetPreferences(r).CVLanguage
|
|
}
|
|
|
|
// GetCVLength retrieves the user's CV length preference
|
|
func GetCVLength(r *http.Request) string {
|
|
return GetPreferences(r).CVLength
|
|
}
|
|
|
|
// GetCVIcons retrieves the user's icon visibility preference
|
|
func GetCVIcons(r *http.Request) string {
|
|
return GetPreferences(r).CVIcons
|
|
}
|
|
|
|
// GetCVTheme retrieves the user's CV theme preference
|
|
func GetCVTheme(r *http.Request) string {
|
|
return GetPreferences(r).CVTheme
|
|
}
|
|
|
|
// GetColorTheme retrieves the user's color theme preference
|
|
func GetColorTheme(r *http.Request) string {
|
|
return GetPreferences(r).ColorTheme
|
|
}
|
|
|
|
// IsLongCV returns true if the user prefers long CV format
|
|
func IsLongCV(r *http.Request) bool {
|
|
return GetCVLength(r) == "long"
|
|
}
|
|
|
|
// IsShortCV returns true if the user prefers short CV format
|
|
func IsShortCV(r *http.Request) bool {
|
|
return GetCVLength(r) == "short"
|
|
}
|
|
|
|
// ShowIcons returns true if icons should be visible
|
|
func ShowIcons(r *http.Request) bool {
|
|
return GetCVIcons(r) == "show"
|
|
}
|
|
|
|
// HideIcons returns true if icons should be hidden
|
|
func HideIcons(r *http.Request) bool {
|
|
return GetCVIcons(r) == "hide"
|
|
}
|
|
|
|
// IsCleanTheme returns true if clean theme is selected
|
|
func IsCleanTheme(r *http.Request) bool {
|
|
return GetCVTheme(r) == "clean"
|
|
}
|
|
|
|
// IsDefaultTheme returns true if default theme is selected
|
|
func IsDefaultTheme(r *http.Request) bool {
|
|
return GetCVTheme(r) == "default"
|
|
}
|
|
|
|
// IsDarkMode returns true if dark mode is enabled
|
|
func IsDarkMode(r *http.Request) bool {
|
|
return GetColorTheme(r) == "dark"
|
|
}
|
|
|
|
// IsLightMode returns true if light mode is enabled
|
|
func IsLightMode(r *http.Request) bool {
|
|
return GetColorTheme(r) == "light"
|
|
}
|
|
|
|
// 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
|
|
}
|