feat: simplify architecture by removing cache layer and centralizing routes
- Removed over-engineered cache system for static CV data that only changes on deployment - Extracted all route configuration to internal/routes/routes.go for better organization - Implemented rate limiting and cache control middleware for PDF endpoint protection
This commit is contained in:
@@ -4,11 +4,7 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/juanatsap/cv-site/internal/cache"
|
||||
)
|
||||
|
||||
// CV represents the complete curriculum vitae structure
|
||||
@@ -16,7 +12,6 @@ type CV struct {
|
||||
Personal Personal `json:"personal"`
|
||||
Summary string `json:"summary"`
|
||||
Experience []Experience `json:"experience"`
|
||||
AIDevelopment AIDevelopment `json:"ai_development"`
|
||||
Education []Education `json:"education"`
|
||||
Skills Skills `json:"skills"`
|
||||
Languages []Language `json:"languages"`
|
||||
@@ -62,20 +57,6 @@ type Experience struct {
|
||||
Duration string `json:"-"` // Calculated field, not from JSON
|
||||
}
|
||||
|
||||
type AIDevelopment struct {
|
||||
Title string `json:"title"`
|
||||
Period string `json:"period"`
|
||||
Description string `json:"description"`
|
||||
Skills []AISkill `json:"skills"`
|
||||
Achievements []string `json:"achievements"`
|
||||
}
|
||||
|
||||
type AISkill struct {
|
||||
Category string `json:"category"`
|
||||
Proficiency string `json:"proficiency"`
|
||||
Items []string `json:"items"`
|
||||
}
|
||||
|
||||
type Education struct {
|
||||
Degree string `json:"degree"`
|
||||
Institution string `json:"institution"`
|
||||
@@ -193,104 +174,42 @@ type TechStack struct {
|
||||
CSS3 string `json:"css3"`
|
||||
}
|
||||
|
||||
// Global cache instance (initialized in main.go)
|
||||
var cvCache *cache.CVCache
|
||||
|
||||
// InitCache initializes the global cache with specified TTL
|
||||
func InitCache(ttl time.Duration) {
|
||||
cvCache = cache.New(ttl)
|
||||
log.Printf("✓ Cache initialized (TTL: %v)", ttl)
|
||||
}
|
||||
|
||||
// GetCache returns the cache instance (for stats/management)
|
||||
func GetCache() *cache.CVCache {
|
||||
return cvCache
|
||||
}
|
||||
|
||||
// LoadCV loads CV data from a JSON file for the specified language
|
||||
// Uses cache if available, falls back to disk on cache miss
|
||||
func LoadCV(lang string) (*CV, error) {
|
||||
// Validate language
|
||||
if lang != "en" && lang != "es" {
|
||||
return nil, fmt.Errorf("unsupported language: %s", lang)
|
||||
}
|
||||
|
||||
// Try cache first if available
|
||||
if cvCache != nil {
|
||||
cacheKey := cache.BuildKey("cv", lang)
|
||||
if cached, found := cvCache.Get(cacheKey); found {
|
||||
if cv, ok := cached.(*CV); ok {
|
||||
return cv, nil
|
||||
}
|
||||
// Invalid cache entry, invalidate it
|
||||
cvCache.Invalidate(cacheKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss or no cache - load from disk
|
||||
filename := fmt.Sprintf("data/cv-%s.json", lang)
|
||||
|
||||
// Read the JSON file
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
var cv CV
|
||||
if err := json.Unmarshal(data, &cv); err != nil {
|
||||
return nil, fmt.Errorf("error parsing JSON: %w", err)
|
||||
}
|
||||
|
||||
// Store in cache if available
|
||||
if cvCache != nil {
|
||||
cacheKey := cache.BuildKey("cv", lang)
|
||||
cvCache.Set(cacheKey, &cv)
|
||||
}
|
||||
|
||||
return &cv, nil
|
||||
}
|
||||
|
||||
// LoadUI loads UI translations from a JSON file for the specified language
|
||||
// Uses cache if available, falls back to disk on cache miss
|
||||
func LoadUI(lang string) (*UI, error) {
|
||||
// Validate language
|
||||
if lang != "en" && lang != "es" {
|
||||
return nil, fmt.Errorf("unsupported language: %s", lang)
|
||||
}
|
||||
|
||||
// Try cache first if available
|
||||
if cvCache != nil {
|
||||
cacheKey := cache.BuildKey("ui", lang)
|
||||
if cached, found := cvCache.Get(cacheKey); found {
|
||||
if ui, ok := cached.(*UI); ok {
|
||||
return ui, nil
|
||||
}
|
||||
// Invalid cache entry, invalidate it
|
||||
cvCache.Invalidate(cacheKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Cache miss or no cache - load from disk
|
||||
filename := fmt.Sprintf("data/ui-%s.json", lang)
|
||||
|
||||
// Read the JSON file
|
||||
data, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error reading file %s: %w", filename, err)
|
||||
}
|
||||
|
||||
// Parse JSON
|
||||
var ui UI
|
||||
if err := json.Unmarshal(data, &ui); err != nil {
|
||||
return nil, fmt.Errorf("error parsing JSON: %w", err)
|
||||
}
|
||||
|
||||
// Store in cache if available
|
||||
if cvCache != nil {
|
||||
cacheKey := cache.BuildKey("ui", lang)
|
||||
cvCache.Set(cacheKey, &ui)
|
||||
}
|
||||
|
||||
return &ui, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user