From 7322c1a15868b1bd51be16f07f985d213b986895 Mon Sep 17 00:00:00 2001 From: juanatsap Date: Mon, 4 May 2026 13:52:53 +0100 Subject: [PATCH] fix: stars count rendered server-side with 30min cache, no client-side API calls --- internal/handlers/cv_helpers.go | 67 ++++++++++++++++++++++- internal/models/cv.go | 1 + internal/models/cv/cv.go | 1 + templates/partials/sections/projects.html | 19 +------ 4 files changed, 69 insertions(+), 19 deletions(-) diff --git a/internal/handlers/cv_helpers.go b/internal/handlers/cv_helpers.go index 00427ae..e6f9359 100644 --- a/internal/handlers/cv_helpers.go +++ b/internal/handlers/cv_helpers.go @@ -1,11 +1,14 @@ package handlers import ( + "encoding/json" "fmt" "log" + "net/http" "os" "path/filepath" "strings" + "sync" "time" "github.com/go-git/go-git/v5" @@ -327,9 +330,12 @@ func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, er ) } - // Process projects for dynamic dates + // Process projects for dynamic dates and fetch GitHub stars for i := range cv.Projects { processProjectDates(&cv.Projects[i], lang) + if cv.Projects[i].OpenSource && cv.Projects[i].GitRepoUrl != "" { + cv.Projects[i].Stars = getGitHubStars(cv.Projects[i].GitRepoUrl) + } } // Split skills between left and right sidebars @@ -383,6 +389,65 @@ func (h *CVHandler) prepareTemplateData(lang string) (map[string]interface{}, er return data, nil } +// ============================================================================== +// GITHUB STARS +// ============================================================================== + +var ( + starsCache = make(map[string]starsCacheEntry) + starsCacheMu sync.RWMutex +) + +type starsCacheEntry struct { + count int + fetched time.Time +} + +// getGitHubStars fetches the star count for a GitHub repo URL, cached for 30 minutes. +func getGitHubStars(repoURL string) int { + // Extract "owner/repo" from URL + repoURL = strings.TrimSuffix(repoURL, "/") + parts := strings.SplitN(repoURL, "github.com/", 2) + if len(parts) != 2 { + return 0 + } + repo := parts[1] + + // Check cache + starsCacheMu.RLock() + if entry, ok := starsCache[repo]; ok && time.Since(entry.fetched) < 30*time.Minute { + starsCacheMu.RUnlock() + return entry.count + } + starsCacheMu.RUnlock() + + // Fetch from GitHub API + client := &http.Client{Timeout: 3 * time.Second} + resp, err := client.Get(fmt.Sprintf("https://api.github.com/repos/%s", repo)) + if err != nil { + return 0 + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != 200 { + return 0 + } + + var result struct { + Stars int `json:"stargazers_count"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return 0 + } + + // Cache the result + starsCacheMu.Lock() + starsCache[repo] = starsCacheEntry{count: result.Stars, fetched: time.Now()} + starsCacheMu.Unlock() + + return result.Stars +} + // ============================================================================== // COOKIE HELPERS // ============================================================================== diff --git a/internal/models/cv.go b/internal/models/cv.go index 78729e5..94b9cc8 100644 --- a/internal/models/cv.go +++ b/internal/models/cv.go @@ -112,6 +112,7 @@ type Project struct { // Computed fields (not stored in JSON) ComputedStartDate string `json:"-"` // Dynamically calculated from git repo or system DynamicDate string `json:"-"` // Current date for ongoing projects + Stars int `json:"-"` // GitHub star count (fetched at runtime) } type Award struct { diff --git a/internal/models/cv/cv.go b/internal/models/cv/cv.go index 572f446..69fd25a 100644 --- a/internal/models/cv/cv.go +++ b/internal/models/cv/cv.go @@ -117,6 +117,7 @@ type Project struct { // Computed fields (not stored in JSON) ComputedStartDate string `json:"-"` // Dynamically calculated from git repo or system DynamicDate string `json:"-"` // Current date for ongoing projects + Stars int `json:"-"` // GitHub star count (fetched at runtime) } type Award struct { diff --git a/templates/partials/sections/projects.html b/templates/partials/sections/projects.html index 11c2144..e1de6fd 100644 --- a/templates/partials/sections/projects.html +++ b/templates/partials/sections/projects.html @@ -32,7 +32,7 @@ {{if .Current}}LIVE{{end}} {{if .GitRepoUrl}}GitHub{{end}} - {{if and .OpenSource .GitRepoUrl}}stars {{end}} + {{if and .OpenSource .GitRepoUrl}}stars{{if .Stars}} {{.Stars}}{{end}}{{end}} {{if .MaintainedBy}}{{$.UI.Sections.MaintainedBy}} {{.MaintainedBy}}{{end}}
{{if .StartDate}}{{.StartDate}}{{if .Current}}{{if .DynamicDate}} / {{.DynamicDate}}{{else}} / {{$.UI.Sections.Present}}{{end}}{{end}}{{end}} - ({{.Location}}) @@ -102,21 +102,4 @@ {{end}} - {{end}}