finally please

This commit is contained in:
Tomas Dvorak
2025-10-25 14:57:59 +02:00
parent 547da76548
commit 857e6f007d
4 changed files with 175 additions and 92 deletions
+3
View File
@@ -95,3 +95,6 @@ UMAMI_URL=https://umami.tdvorak.dev
UMAMI_USERNAME=admin UMAMI_USERNAME=admin
UMAMI_PASSWORD=eevRQ6h3G@!c#y4A1T UMAMI_PASSWORD=eevRQ6h3G@!c#y4A1T
UMAMI_WEBSITE_ID= UMAMI_WEBSITE_ID=
PREFETCH_TARGET=http://127.0.0.1:8080/api/v1
+1
View File
@@ -18,6 +18,7 @@ services:
- PORT=8080 - PORT=8080
- RUN_MIGRATIONS=true - RUN_MIGRATIONS=true
- SEED_DATABASE=false - SEED_DATABASE=false
- PREFETCH_TARGET=http://localhost:8080/api/v1
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
+5 -22
View File
@@ -1913,14 +1913,6 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
} }
} }
// Trigger background prefetch and YouTube cache refresh when settings are updated post-setup // Trigger background prefetch and YouTube cache refresh when settings are updated post-setup
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
host := c.Request.Host
if host != "" {
baseURL := scheme + "://" + host + "/api/v1"
// Best-effort: immediately write public settings cache from current Settings
go func(snap models.Settings) { go func(snap models.Settings) {
defer func() { _ = recover() }() defer func() { _ = recover() }()
snap.LoadCustomNav() snap.LoadCustomNav()
@@ -1983,8 +1975,7 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
_ = os.WriteFile(tmp, b, 0o644) _ = os.WriteFile(tmp, b, 0o644)
_ = os.Rename(tmp, outPath) _ = os.Rename(tmp, outPath)
}(s) }(s)
go services.PrefetchOnce(baseURL) go services.PrefetchOnce(getPrefetchBaseURL())
}
if strings.TrimSpace(s.YoutubeURL) != "" { if strings.TrimSpace(s.YoutubeURL) != "" {
go func(u string) { _ = services.RefreshYouTubeChannelNow(u) }(s.YoutubeURL) go func(u string) { _ = services.RefreshYouTubeChannelNow(u) }(s.YoutubeURL)
} }
@@ -2245,7 +2236,7 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
logger.Info("Initial settings saved: club_id=%s club_name=%s gallery_url=%s gallery_label=%s", s.ClubID, s.ClubName, s.GalleryURL, s.GalleryLabel) logger.Info("Initial settings saved: club_id=%s club_name=%s gallery_url=%s gallery_label=%s", s.ClubID, s.ClubName, s.GalleryURL, s.GalleryLabel)
// Immediately write public settings cache from current Settings snapshot // Immediately write public settings cache from current Settings snapshot
func() { go func() {
defer func() { _ = recover() }() defer func() { _ = recover() }()
s.LoadCustomNav() s.LoadCustomNav()
var pubVids []string var pubVids []string
@@ -2313,28 +2304,20 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
logger.Info("Default homepage page elements seeded") logger.Info("Default homepage page elements seeded")
// Run all setup operations asynchronously in background to provide immediate response // Run all setup operations asynchronously in background to provide immediate response
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
host := c.Request.Host
logger.Info("Starting initial data prefetch and setup operations in background...") logger.Info("Starting initial data prefetch and setup operations in background...")
// Run all setup operations in a single background goroutine // Run all setup operations in a single background goroutine
go func(settingsID uint, youtubeURL, galleryURL, adminEmail, baseHost string) { go func(settingsID uint, youtubeURL, galleryURL, adminEmail string) {
defer func() { _ = recover() }() defer func() { _ = recover() }()
// 1. Trigger prefetch (matches, standings, etc.) // 1. Trigger prefetch (matches, standings, etc.)
if baseHost != "" { baseURL := getPrefetchBaseURL()
baseURL := scheme + "://" + baseHost + "/api/v1"
services.PrefetchOnce(baseURL) services.PrefetchOnce(baseURL)
logger.Info("Background prefetch completed") logger.Info("Background prefetch completed")
// Auto-populate competition aliases from FACR data // Auto-populate competition aliases from FACR data
bc.autoPopulateCompetitionAliases() bc.autoPopulateCompetitionAliases()
logger.Info("Background competition aliases populated") logger.Info("Background competition aliases populated")
}
// 2. If YouTube channel is configured, refresh its cache // 2. If YouTube channel is configured, refresh its cache
if strings.TrimSpace(youtubeURL) != "" { if strings.TrimSpace(youtubeURL) != "" {
@@ -2368,7 +2351,7 @@ func (bc *BaseController) SetupInitialize(c *gin.Context) {
} }
logger.Info("All background setup operations completed") logger.Info("All background setup operations completed")
}(s.ID, s.YoutubeURL, s.GalleryURL, admin.Email, host) }(s.ID, s.YoutubeURL, s.GalleryURL, admin.Email)
logger.Info("SetupInitialize finished successfully - background operations running") logger.Info("SetupInitialize finished successfully - background operations running")
c.JSON(http.StatusOK, gin.H{"message": "Setup completed successfully"}) c.JSON(http.StatusOK, gin.H{"message": "Setup completed successfully"})
+99 -3
View File
@@ -542,9 +542,81 @@ func doPrefetchCycle(client *http.Client, baseURL string) {
} }
} }
// Alias: keep legacy/frontend callers happy expecting matches.json // Prepare matches.json from FACR data if available; fallback to events_upcoming.json
if b, err := os.ReadFile(filepath.Join(cacheDir, "events_upcoming.json")); err == nil { createdMatches := false
_ = os.WriteFile(filepath.Join(cacheDir, "matches.json"), b, 0o644) buildMatchesFromFACR := func(data []byte) bool {
type facrMatch struct {
DateTime string `json:"date_time"`
Home string `json:"home"`
Away string `json:"away"`
Venue string `json:"venue"`
HomeLogoURL string `json:"home_logo_url"`
AwayLogoURL string `json:"away_logo_url"`
MatchID string `json:"match_id"`
}
var facr struct {
Competitions []struct {
Name string `json:"name"`
Code string `json:"code"`
Matches []facrMatch `json:"matches"`
} `json:"competitions"`
}
if err := json.Unmarshal(data, &facr); err != nil {
return false
}
// Collect upcoming matches (next 30 days) in simplified format
now := time.Now()
max := now.Add(30 * 24 * time.Hour)
var out []map[string]any
for _, c := range facr.Competitions {
compName := strings.TrimSpace(c.Name)
if compName == "" {
compName = strings.TrimSpace(c.Code)
}
for _, m := range c.Matches {
dt := strings.TrimSpace(m.DateTime)
if dt == "" {
continue
}
// dt like "12.08.2023 18:00"
parts := strings.SplitN(dt, " ", 2)
d := parts[0]
t := ""
if len(parts) > 1 { t = parts[1] }
// parse date dd.mm.yyyy
dd := strings.Split(d, ".")
if len(dd) < 3 { continue }
day := dd[0]
month := dd[1]
year := dd[2]
if len(month) == 1 { month = "0" + month }
if len(day) == 1 { day = "0" + day }
isoDate := year + "-" + month + "-" + day
if len(t) >= 5 {
t = t[:5]
} else {
t = "18:00"
}
// Build time.Time for filtering
ts, err := time.ParseInLocation("2006-01-02 15:04", isoDate+" "+t, time.Local)
if err != nil { continue }
if ts.Before(now) || ts.After(max) { continue }
out = append(out, map[string]any{
"id": m.MatchID,
"home": m.Home,
"away": m.Away,
"competition": compName,
"date": isoDate,
"time": t,
"venue": m.Venue,
"home_logo_url": m.HomeLogoURL,
"away_logo_url": m.AwayLogoURL,
})
}
}
if len(out) == 0 { return false }
_ = writeJSONAtomic(filepath.Join(cacheDir, "matches.json"), out)
return true
} }
// 2) Dynamic FACR endpoints based on saved settings // 2) Dynamic FACR endpoints based on saved settings
@@ -582,10 +654,34 @@ func doPrefetchCycle(client *http.Client, baseURL string) {
statuses = append(statuses, epStatus{Path: path, File: file, Ok: true}) statuses = append(statuses, epStatus{Path: path, File: file, Ok: true})
} }
} }
// Try to build matches.json from freshly fetched FACR prefetch file
if b, err := os.ReadFile(filepath.Join(cacheDir, "facr_club_info.json")); err == nil {
if buildMatchesFromFACR(b) { createdMatches = true }
}
} else { } else {
log.Printf("[prefetch] WARNING: FACR skipped: missing club_id=%q or club_type=%q in settings", clubID, clubType) log.Printf("[prefetch] WARNING: FACR skipped: missing club_id=%q or club_type=%q in settings", clubID, clubType)
} }
// If FACR prefetch not available, try internal FACR cache as fallback
if !createdMatches && clubID != "" && clubType != "" {
facrCachePath := filepath.Join("cache", "facr", fmt.Sprintf("%s_%s_info.json", clubType, clubID))
if b, err := os.ReadFile(facrCachePath); err == nil {
var cached struct{ Data []byte `json:"data"` }
if jsonErr := json.Unmarshal(b, &cached); jsonErr == nil && len(cached.Data) > 0 {
if buildMatchesFromFACR(cached.Data) { createdMatches = true }
}
}
}
// Final fallback: copy events_upcoming.json or write empty list
if !createdMatches {
if b, err := os.ReadFile(filepath.Join(cacheDir, "events_upcoming.json")); err == nil {
_ = os.WriteFile(filepath.Join(cacheDir, "matches.json"), b, 0o644)
} else {
_ = os.WriteFile(filepath.Join(cacheDir, "matches.json"), []byte("[]"), 0o644)
}
}
// Meta timestamp // Meta timestamp
_ = os.WriteFile(filepath.Join(cacheDir, "meta.json"), []byte(fmt.Sprintf(`{"lastUpdated":"%s"}`, time.Now().Format(time.RFC3339))), 0o644) _ = os.WriteFile(filepath.Join(cacheDir, "meta.json"), []byte(fmt.Sprintf(`{"lastUpdated":"%s"}`, time.Now().Format(time.RFC3339))), 0o644)
// Detailed status for admin/debugging // Detailed status for admin/debugging