docs: Add comprehensive backend handler documentation

Create public-facing documentation explaining backend architecture:

New Documentation:
- doc/14-BACKEND-HANDLERS.md (900+ lines)
  * Handler architecture and file organization
  * Request/response type system with examples
  * Middleware pattern and preferences handling
  * Comprehensive testing strategy
  * Data flow diagrams and best practices
  * Code examples for all major patterns

Updated:
- doc/README.md
  * Add Backend Handlers to technical implementation section
  * Update total active docs count (13 → 14)
  * Add quick navigation links

Content Coverage:
- Handler responsibilities (pages, PDF, HTMX)
- Type-safe request handling with validation
- Middleware architecture and context usage
- Test coverage across all handler types
- Request processing flow diagrams
- Best practices with do/don't examples

Audience:
- Backend developers
- API consumers
- New contributors
- Technical documentation readers

Complements:
- Educational docs in _go-learning/refactorings/
- Internal architecture documentation
- API reference guide
This commit is contained in:
juanatsap
2025-11-20 17:35:58 +00:00
parent 8a709c6863
commit 025c10ac1f
2 changed files with 569 additions and 1 deletions
+566
View File
@@ -0,0 +1,566 @@
# Backend Handler Architecture
**Last Updated**: November 20, 2024
## Overview
This document explains how the backend handles HTTP requests, focusing on the handler architecture, type safety, middleware pattern, and testing strategy implemented in the CV website.
## Table of Contents
1. [Handler Architecture](#handler-architecture)
2. [Request/Response Types](#requestresponse-types)
3. [Middleware Pattern](#middleware-pattern)
4. [Testing Strategy](#testing-strategy)
5. [Data Flow](#data-flow)
6. [Best Practices](#best-practices)
---
## Handler Architecture
### File Organization
The handler layer is organized by responsibility into focused files:
```
internal/handlers/
├── cv.go # Core handler struct and constructor
├── cv_pages.go # Page rendering handlers
├── cv_pdf.go # PDF export handler
├── cv_htmx.go # HTMX toggle handlers
├── cv_helpers.go # Shared helper functions
├── types.go # Request/response types
├── errors.go # Error handling utilities
└── *_test.go # Comprehensive test suites
```
### Handler Responsibilities
#### 1. Page Handlers (cv_pages.go)
**Purpose**: Render full HTML pages and content sections
```go
// Home - Renders the complete CV page with all content
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request)
// CVContent - Renders CV content for HTMX swaps
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request)
// DefaultCVShortcut - Handles shortcut URLs like /cv-jamr-2025-en.pdf
func (h *CVHandler) DefaultCVShortcut(w http.ResponseWriter, r *http.Request)
```
**Example Flow**:
```
Browser Request → Home() → prepareTemplateData() → Render HTML → Response
```
#### 2. PDF Handler (cv_pdf.go)
**Purpose**: Generate PDF exports with customizable options
```go
// ExportPDF - Generates PDF with parameters: lang, length, icons, version
func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request)
```
**Features**:
- Multi-language support (English, Spanish)
- Length variants (short, long)
- Icon visibility toggle (show, hide)
- Theme variants (default with skills, clean without skills)
- Smart filename generation
- Print-optimized CSS rendering
**Example Request**:
```bash
GET /export-pdf?lang=es&length=long&icons=show&version=with_skills
```
#### 3. HTMX Toggle Handlers (cv_htmx.go)
**Purpose**: Handle interactive toggles via HTMX
```go
// ToggleLength - Toggle between short and long CV
func (h *CVHandler) ToggleLength(w http.ResponseWriter, r *http.Request)
// ToggleIcons - Show/hide skill and tool icons
func (h *CVHandler) ToggleIcons(w http.ResponseWriter, r *http.Request)
// SwitchLanguage - Switch between English and Spanish
func (h *CVHandler) SwitchLanguage(w http.ResponseWriter, r *http.Request)
// ToggleTheme - Toggle between default and clean theme
func (h *CVHandler) ToggleTheme(w http.ResponseWriter, r *http.Request)
```
**HTMX Pattern**:
1. User clicks toggle button
2. HTMX sends POST request
3. Handler updates cookie
4. Handler returns HTML fragment with out-of-band swaps
5. HTMX swaps multiple DOM elements atomically
---
## Request/Response Types
### Type-Safe Request Handling
Instead of manually parsing query parameters, we use structured types with validation:
#### PDF Export Request
```go
// PDFExportRequest represents all PDF export parameters
type PDFExportRequest struct {
Lang string // "en" or "es"
Length string // "short" or "long"
Icons string // "show" or "hide"
Version string // "with_skills" or "clean"
}
// Parse and validate in one call
req, err := ParsePDFExportRequest(r)
if err != nil {
// Return 400 Bad Request with clear error message
HandleError(w, r, BadRequestError(err.Error()))
return
}
// Type-safe access
filename := fmt.Sprintf("cv-%s-%s.pdf", req.Length, req.Lang)
```
#### Benefits
**Type Safety**: Compile-time guarantees prevent typos
**Self-Documenting**: Struct fields show all valid parameters
**Centralized Validation**: One place to update validation rules
**Clear Errors**: Descriptive error messages for invalid requests
**Example Validation**:
```go
// Automatic validation with helpful error messages
GET /export-pdf?lang=fr
400 Bad Request: "unsupported language: fr (use 'en' or 'es')"
GET /export-pdf?length=medium
400 Bad Request: "unsupported length: medium (use 'short' or 'long')"
```
#### Language Request
```go
// LanguageRequest for endpoints that only need language
type LanguageRequest struct {
Lang string // "en" or "es"
}
// Usage
req, err := ParseLanguageRequest(r)
// Defaults to "en" if not specified
// Validates against supported languages
```
---
## Middleware Pattern
### Preferences Middleware
**Purpose**: Read user preferences from cookies once and make them available via context
#### Architecture
```
Request
PreferencesMiddleware
├─ Read all preference cookies
├─ Migrate old values (extended → long, true → show)
├─ Store in request context
└─ Pass to next handler
Handler
├─ Get preferences from context
├─ No cookie reading needed
└─ Use preferences in business logic
Response
```
#### Implementation
```go
// Middleware reads cookies and stores in context
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"),
}
// Automatic migration of old preference values
if prefs.CVLength == "extended" {
prefs.CVLength = "long"
}
// Store in context for handlers
ctx := context.WithValue(r.Context(), PreferencesKey, prefs)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
// Handlers access preferences via context
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
// Get preferences from context (already read by middleware)
prefs := middleware.GetPreferences(r)
// Use preferences
cvLengthClass := "cv-short"
if prefs.CVLength == "long" {
cvLengthClass = "cv-long"
}
}
```
#### Benefits
**Performance**: Cookies read once per request
**Consistency**: All handlers get same preference values
**Maintainability**: Migration logic in one place
**Testability**: Easy to mock preferences via context
---
## Testing Strategy
### Test Coverage
The handler layer has comprehensive test coverage across multiple files:
```
internal/handlers/
├── cv_pages_test.go # Page handler tests
├── cv_htmx_test.go # HTMX toggle tests
├── pdf_test.go # PDF generation tests (integration)
└── cv_security_test.go # Security validation tests
```
### Page Handler Tests
**File**: `cv_pages_test.go`
**Test Cases**: 15+
**Coverage**: Language validation, rendering, shortcuts
```go
// Example test structure
func TestHome(t *testing.T) {
tests := []struct {
name string
lang string
expectStatus int
expectContains string
}{
{
name: "Default language (English)",
lang: "",
expectStatus: http.StatusOK,
expectContains: "Juan Andrés Moreno Rubio",
},
{
name: "Invalid language",
lang: "fr",
expectStatus: http.StatusBadRequest,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test implementation
})
}
}
```
### HTMX Handler Tests
**File**: `cv_htmx_test.go`
**Test Cases**: 20+
**Coverage**: Toggles, cookies, method validation, migrations
```go
// Example: Testing toggle behavior
func TestToggleLength(t *testing.T) {
tests := []struct {
name string
currentLength string
expectedToggle string
}{
{
name: "Toggle from short to long",
currentLength: "short",
expectedToggle: "long",
},
{
name: "Migration: extended → long",
currentLength: "extended",
expectedToggle: "short", // extended becomes long, then toggles
},
}
// ...
}
```
### Method Validation Tests
All HTMX endpoints enforce POST-only requests:
```go
func TestHTMXHandlersRequirePost(t *testing.T) {
// Tests verify GET requests return 405 Method Not Allowed
handlers := []struct {
name string
handler func(http.ResponseWriter, *http.Request)
}{
{"ToggleLength", handler.ToggleLength},
{"ToggleIcons", handler.ToggleIcons},
{"ToggleTheme", handler.ToggleTheme},
}
// All should reject GET with 405
for _, h := range handlers {
req := httptest.NewRequest(http.MethodGet, "/endpoint", nil)
w := httptest.NewRecorder()
h.handler(w, req)
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
}
}
```
### Running Tests
```bash
# Run all unit tests (excludes PDF generation)
go test -short ./...
# Run specific handler tests
go test -short ./internal/handlers/... -v
# Run all tests including integration tests
make test-all
# Pre-commit hook runs tests automatically
git commit -m "changes" # Tests run before commit
```
---
## Data Flow
### Request Processing Flow
```
1. Client Request
├─ Browser/HTMX makes HTTP request
└─ URL: /export-pdf?lang=es&length=long
2. Middleware Chain
├─ Recovery (catch panics)
├─ Logger (request logging)
├─ Security Headers (CSP, HSTS)
└─ PreferencesMiddleware (read cookies)
3. Router
├─ Match URL pattern
└─ Dispatch to handler
4. Handler
├─ Parse request (type-safe)
│ └─ ParsePDFExportRequest(r)
├─ Validate parameters
│ └─ Return 400 if invalid
├─ Prepare data
│ └─ prepareTemplateData(lang)
├─ Generate response
│ └─ Render template or generate PDF
└─ Return response
5. Client Response
├─ HTML page
├─ HTMX fragment
├─ PDF download
└─ Error page
```
### Template Data Preparation
Central helper function used by multiple handlers:
```go
func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, error) {
// Load CV data (cached)
cv, err := cvmodel.LoadCV(lang)
if err != nil {
return nil, err
}
// Load UI translations (cached)
ui, err := uimodel.LoadUI(lang)
if err != nil {
return nil, err
}
// Calculate dynamic data
for i := range cv.Experience {
cv.Experience[i].Duration = calculateDuration(
cv.Experience[i].StartDate,
cv.Experience[i].EndDate,
cv.Experience[i].Current,
lang,
)
}
// Process projects
for i := range cv.Projects {
processProjectDates(&cv.Projects[i], lang)
}
// Prepare skills
skillsLeft, skillsRight := splitSkills(cv.Skills.Technical)
// Return complete data map
return map[string]interface{}{
"CV": cv,
"UI": ui,
"Lang": lang,
"SkillsLeft": skillsLeft,
"SkillsRight": skillsRight,
"YearsOfExperience": calculateYearsOfExperience(),
"CurrentYear": time.Now().Year(),
"CanonicalURL": fmt.Sprintf("https://juan.andres.morenorub.io/?lang=%s", lang),
"AlternateEN": "https://juan.andres.morenorub.io/?lang=en",
"AlternateES": "https://juan.andres.morenorub.io/?lang=es",
}, nil
}
```
---
## Best Practices
### 1. Type-Safe Requests
**DO**: Use structured request types
```go
req, err := ParsePDFExportRequest(r)
if err != nil {
HandleError(w, r, BadRequestError(err.Error()))
return
}
```
**DON'T**: Manually parse parameters
```go
lang := r.URL.Query().Get("lang")
if lang == "" { lang = "en" }
if lang != "en" && lang != "es" {
// Repetitive validation code
}
```
### 2. Centralized Validation
**DO**: Validate in request parser
```go
func ParsePDFExportRequest(r *http.Request) (*PDFExportRequest, error) {
req := &PDFExportRequest{ /* parse */ }
// All validation in one place
if req.Lang != "en" && req.Lang != "es" {
return nil, fmt.Errorf("unsupported language: %s", req.Lang)
}
return req, nil
}
```
**DON'T**: Scatter validation across handlers
```go
// Validation duplicated in multiple places
```
### 3. Reuse Helper Functions
**DO**: Use shared data preparation
```go
func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
data, err := h.prepareTemplateData(lang)
// Add page-specific fields
}
func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
data, err := h.prepareTemplateData(lang)
// Reuse same data preparation
}
```
**DON'T**: Duplicate data preparation logic
```go
// 100+ lines duplicated across handlers
```
### 4. Test All Handlers
**DO**: Write comprehensive tests
```go
func TestToggleLength(t *testing.T) {
// Test toggle behavior
// Test cookie persistence
// Test migration from old values
}
```
**DO**: Test error cases
```go
func TestInvalidLanguage(t *testing.T) {
// Verify 400 Bad Request
// Check error message
}
```
### 5. Use Middleware for Cross-Cutting Concerns
**DO**: Extract common logic to middleware
```go
// PreferencesMiddleware reads cookies once
// Handlers get preferences from context
```
**DON'T**: Read cookies in every handler
```go
// Cookie reading duplicated across handlers
```
---
## Related Documentation
- [Architecture Overview](./1-ARCHITECTURE.md) - System architecture patterns
- [API Reference](./3-API.md) - Complete API documentation
- [Security](./9-SECURITY.md) - Security implementation details
- [PDF Export](./11-PDF-EXPORT.md) - PDF generation details
- [Testing Guide](../_go-learning/refactorings/) - Detailed refactoring documentation
---
## Changelog
- **Nov 20, 2024**: Initial documentation covering handler refactoring, type safety, middleware pattern, and testing strategy
+3 -1
View File
@@ -18,6 +18,7 @@
- [5. Zoom Implementation](5-ZOOM-IMPLEMENTATION.md) - Custom zoom feature technical details - [5. Zoom Implementation](5-ZOOM-IMPLEMENTATION.md) - Custom zoom feature technical details
- [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization ⭐ - [12. CSS Architecture](12-CSS-ARCHITECTURE.md) - Modular CSS structure and ITCSS organization ⭐
- [13. Toast Notifications](13-TOAST-NOTIFICATIONS.md) - Toast notification system for PDF downloads and user feedback - [13. Toast Notifications](13-TOAST-NOTIFICATIONS.md) - Toast notification system for PDF downloads and user feedback
- [14. Backend Handlers](14-BACKEND-HANDLERS.md) - Handler architecture, type safety, middleware, and testing ⭐
**Deployment & Operations** **Deployment & Operations**
- [8. Deployment Guide](8-DEPLOYMENT.md) - Production deployment instructions - [8. Deployment Guide](8-DEPLOYMENT.md) - Production deployment instructions
@@ -46,6 +47,7 @@
| 5 | [ZOOM_IMPLEMENTATION.md](5-ZOOM-IMPLEMENTATION.md) | Zoom feature implementation details | Feature developers | | 5 | [ZOOM_IMPLEMENTATION.md](5-ZOOM-IMPLEMENTATION.md) | Zoom feature implementation details | Feature developers |
| 12 | [CSS-ARCHITECTURE.md](12-CSS-ARCHITECTURE.md) | Modular CSS structure, ITCSS layers, HTMX integration | Frontend developers, designers | | 12 | [CSS-ARCHITECTURE.md](12-CSS-ARCHITECTURE.md) | Modular CSS structure, ITCSS layers, HTMX integration | Frontend developers, designers |
| 13 | [TOAST-NOTIFICATIONS.md](13-TOAST-NOTIFICATIONS.md) | Toast notification system, PDF download feedback, user notifications | Frontend developers, UX designers | | 13 | [TOAST-NOTIFICATIONS.md](13-TOAST-NOTIFICATIONS.md) | Toast notification system, PDF download feedback, user notifications | Frontend developers, UX designers |
| 14 | [BACKEND-HANDLERS.md](14-BACKEND-HANDLERS.md) | Handler architecture, type safety, middleware pattern, testing strategy | Backend developers |
### User & Operations Documentation ### User & Operations Documentation
@@ -141,4 +143,4 @@ All documentation in this project follows these standards:
**Last Updated**: 2025-11-20 **Last Updated**: 2025-11-20
**Documentation Status**: ✅ Clean, organized, zero redundancy **Documentation Status**: ✅ Clean, organized, zero redundancy
**Total Active Docs**: 13 core documents + archive **Total Active Docs**: 14 core documents + archive