refactor: Separate CV domain and UI presentation models into distinct packages
**Main Changes:** 1. **Package Restructuring** - Separated mixed concerns into focused packages: - Created `internal/models/cv/` for CV domain logic (CV, Personal, Experience, etc.) - Created `internal/models/ui/` for UI presentation logic (InfoModal, ShortcutsModal, etc.) - Removed monolithic `internal/models/cv.go` (300+ lines → organized packages) 2. **Testing** - Added comprehensive unit tests: - `internal/models/cv/loader_test.go` - CV data loading and validation - `internal/models/ui/loader_test.go` - UI translations loading - All tests passing ✅ 3. **Documentation** - Added Go learning knowledge base: - `_go-learning/architecture/server-design.md` - Goroutines, graceful shutdown explained - `_go-learning/refactorings/001-cv-model-separation.md` - This refactoring documented - Public documentation showcasing Go expertise (README.md kept private) 4. **Handler Updates** - Updated imports to use new package structure: - `internal/handlers/cv.go` - Uses `cvmodel` and `uimodel` aliases **Benefits:** - ✅ Clear separation of concerns (domain vs presentation) - ✅ Better testability (isolated package testing) - ✅ Improved maintainability (smaller, focused files) - ✅ Scalability (each domain can evolve independently) - ✅ Follows Go best practices (small, cohesive packages) **Other Updates:** - Updated middleware security checks - Template improvements - Organized completed prompts **Testing:** - All Go unit tests pass (cv, ui, handlers) - Server verified with curl tests (English, Spanish, Health endpoints) - Frontend functionality unchanged (refactoring is transparent to UI)
This commit is contained in:
+11
-10
@@ -11,7 +11,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/juanatsap/cv-site/internal/models"
|
||||
cvmodel "github.com/juanatsap/cv-site/internal/models/cv"
|
||||
uimodel "github.com/juanatsap/cv-site/internal/models/ui"
|
||||
"github.com/juanatsap/cv-site/internal/pdf"
|
||||
"github.com/juanatsap/cv-site/internal/templates"
|
||||
)
|
||||
@@ -53,14 +54,14 @@ func (h *CVHandler) Home(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Load CV data
|
||||
cv, err := models.LoadCV(lang)
|
||||
cv, err := cvmodel.LoadCV(lang)
|
||||
if err != nil {
|
||||
HandleError(w, r, DataLoadError(err, "CV"))
|
||||
return
|
||||
}
|
||||
|
||||
// Load UI translations
|
||||
ui, err := models.LoadUI(lang)
|
||||
ui, err := uimodel.LoadUI(lang)
|
||||
if err != nil {
|
||||
HandleError(w, r, DataLoadError(err, "UI"))
|
||||
return
|
||||
@@ -161,14 +162,14 @@ func (h *CVHandler) CVContent(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Load CV data
|
||||
cv, err := models.LoadCV(lang)
|
||||
cv, err := cvmodel.LoadCV(lang)
|
||||
if err != nil {
|
||||
HandleError(w, r, DataLoadError(err, "CV"))
|
||||
return
|
||||
}
|
||||
|
||||
// Load UI translations
|
||||
ui, err := models.LoadUI(lang)
|
||||
ui, err := uimodel.LoadUI(lang)
|
||||
if err != nil {
|
||||
HandleError(w, r, DataLoadError(err, "UI"))
|
||||
return
|
||||
@@ -345,7 +346,7 @@ func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("PDF export requested: lang=%s, length=%s, icons=%s, version=%s", lang, length, icons, version)
|
||||
|
||||
// Load CV data to get name for filename
|
||||
cv, err := models.LoadCV(lang)
|
||||
cv, err := cvmodel.LoadCV(lang)
|
||||
if err != nil {
|
||||
HandleError(w, r, DataLoadError(err, "CV"))
|
||||
return
|
||||
@@ -441,7 +442,7 @@ func (h *CVHandler) ExportPDF(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// splitSkills splits skill categories between left (page 1) and right (page 2) sidebars
|
||||
// Each category explicitly specifies which sidebar it belongs to via the "sidebar" field
|
||||
func splitSkills(skills []models.SkillCategory) (left, right []models.SkillCategory) {
|
||||
func splitSkills(skills []cvmodel.SkillCategory) (left, right []cvmodel.SkillCategory) {
|
||||
if len(skills) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -570,7 +571,7 @@ func calculateDuration(startDate, endDate string, current bool, lang string) str
|
||||
// processProjectDates calculates dynamic dates for projects
|
||||
// If a project has a gitRepoUrl, it fetches the first commit date
|
||||
// For current projects, it sets the current system date
|
||||
func processProjectDates(project *models.Project, lang string) {
|
||||
func processProjectDates(project *cvmodel.Project, lang string) {
|
||||
now := time.Now()
|
||||
|
||||
// Set dynamic current date for ongoing projects
|
||||
@@ -718,13 +719,13 @@ func getGitRepoFirstCommitDate(repoPath string) string {
|
||||
// prepareTemplateData prepares common template data used across handlers
|
||||
func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, error) {
|
||||
// Load CV data
|
||||
cv, err := models.LoadCV(lang)
|
||||
cv, err := cvmodel.LoadCV(lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Load UI translations
|
||||
ui, err := models.LoadUI(lang)
|
||||
ui, err := uimodel.LoadUI(lang)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user