mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
finally please
This commit is contained in:
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -1913,78 +1913,69 @@ 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"
|
go func(snap models.Settings) {
|
||||||
if c.Request.TLS != nil {
|
defer func() { _ = recover() }()
|
||||||
scheme = "https"
|
snap.LoadCustomNav()
|
||||||
}
|
var pubVids []string
|
||||||
host := c.Request.Host
|
if snap.VideosJSON != "" { _ = json.Unmarshal([]byte(snap.VideosJSON), &pubVids) }
|
||||||
if host != "" {
|
var pubVidsItems any
|
||||||
baseURL := scheme + "://" + host + "/api/v1"
|
if snap.VideosItemsJSON != "" { _ = json.Unmarshal([]byte(snap.VideosItemsJSON), &pubVidsItems) }
|
||||||
// Best-effort: immediately write public settings cache from current Settings
|
var pubMerchItems any
|
||||||
go func(snap models.Settings) {
|
if snap.MerchItemsJSON != "" { _ = json.Unmarshal([]byte(snap.MerchItemsJSON), &pubMerchItems) }
|
||||||
defer func() { _ = recover() }()
|
resp := map[string]any{
|
||||||
snap.LoadCustomNav()
|
"club_id": snap.ClubID,
|
||||||
var pubVids []string
|
"club_type": snap.ClubType,
|
||||||
if snap.VideosJSON != "" { _ = json.Unmarshal([]byte(snap.VideosJSON), &pubVids) }
|
"club_name": snap.ClubName,
|
||||||
var pubVidsItems any
|
"club_logo_url": snap.ClubLogoURL,
|
||||||
if snap.VideosItemsJSON != "" { _ = json.Unmarshal([]byte(snap.VideosItemsJSON), &pubVidsItems) }
|
"club_url": snap.ClubURL,
|
||||||
var pubMerchItems any
|
"primary_color": snap.PrimaryColor,
|
||||||
if snap.MerchItemsJSON != "" { _ = json.Unmarshal([]byte(snap.MerchItemsJSON), &pubMerchItems) }
|
"secondary_color": snap.SecondaryColor,
|
||||||
resp := map[string]any{
|
"accent_color": snap.AccentColor,
|
||||||
"club_id": snap.ClubID,
|
"background_color": snap.BackgroundColor,
|
||||||
"club_type": snap.ClubType,
|
"text_color": snap.TextColor,
|
||||||
"club_name": snap.ClubName,
|
"font_heading": snap.FontHeading,
|
||||||
"club_logo_url": snap.ClubLogoURL,
|
"font_body": snap.FontBody,
|
||||||
"club_url": snap.ClubURL,
|
"sponsors_layout": snap.SponsorsLayout,
|
||||||
"primary_color": snap.PrimaryColor,
|
"sponsors_theme": snap.SponsorsTheme,
|
||||||
"secondary_color": snap.SecondaryColor,
|
"facebook_url": snap.FacebookURL,
|
||||||
"accent_color": snap.AccentColor,
|
"instagram_url": snap.InstagramURL,
|
||||||
"background_color": snap.BackgroundColor,
|
"youtube_url": snap.YoutubeURL,
|
||||||
"text_color": snap.TextColor,
|
"gallery_url": snap.GalleryURL,
|
||||||
"font_heading": snap.FontHeading,
|
"gallery_label": snap.GalleryLabel,
|
||||||
"font_body": snap.FontBody,
|
"videos_module_enabled": snap.VideosModuleEnabled,
|
||||||
"sponsors_layout": snap.SponsorsLayout,
|
"videos_style": snap.VideosStyle,
|
||||||
"sponsors_theme": snap.SponsorsTheme,
|
"videos_source": snap.VideosSource,
|
||||||
"facebook_url": snap.FacebookURL,
|
"videos_limit": snap.VideosLimit,
|
||||||
"instagram_url": snap.InstagramURL,
|
"videos": pubVids,
|
||||||
"youtube_url": snap.YoutubeURL,
|
"videos_items": pubVidsItems,
|
||||||
"gallery_url": snap.GalleryURL,
|
"merch_module_enabled": snap.MerchModuleEnabled,
|
||||||
"gallery_label": snap.GalleryLabel,
|
"merch_style": snap.MerchStyle,
|
||||||
"videos_module_enabled": snap.VideosModuleEnabled,
|
"merch_source": snap.MerchSource,
|
||||||
"videos_style": snap.VideosStyle,
|
"merch_limit": snap.MerchLimit,
|
||||||
"videos_source": snap.VideosSource,
|
"merch_items": pubMerchItems,
|
||||||
"videos_limit": snap.VideosLimit,
|
"about_html": snap.AboutHTML,
|
||||||
"videos": pubVids,
|
"show_about_in_nav": snap.ShowAboutInNav,
|
||||||
"videos_items": pubVidsItems,
|
"custom_nav": snap.CustomNav,
|
||||||
"merch_module_enabled": snap.MerchModuleEnabled,
|
"contact_address": snap.ContactAddress,
|
||||||
"merch_style": snap.MerchStyle,
|
"contact_city": snap.ContactCity,
|
||||||
"merch_source": snap.MerchSource,
|
"contact_zip": snap.ContactZip,
|
||||||
"merch_limit": snap.MerchLimit,
|
"contact_country": snap.ContactCountry,
|
||||||
"merch_items": pubMerchItems,
|
"contact_phone": snap.ContactPhone,
|
||||||
"about_html": snap.AboutHTML,
|
"contact_email": snap.ContactEmail,
|
||||||
"show_about_in_nav": snap.ShowAboutInNav,
|
"location_latitude": snap.LocationLatitude,
|
||||||
"custom_nav": snap.CustomNav,
|
"location_longitude": snap.LocationLongitude,
|
||||||
"contact_address": snap.ContactAddress,
|
"map_zoom_level": snap.MapZoomLevel,
|
||||||
"contact_city": snap.ContactCity,
|
"map_style": snap.MapStyle,
|
||||||
"contact_zip": snap.ContactZip,
|
"show_map_on_homepage": snap.ShowMapOnHomepage,
|
||||||
"contact_country": snap.ContactCountry,
|
}
|
||||||
"contact_phone": snap.ContactPhone,
|
b, _ := json.MarshalIndent(resp, "", " ")
|
||||||
"contact_email": snap.ContactEmail,
|
outPath := filepath.Join("cache", "prefetch", "settings.json")
|
||||||
"location_latitude": snap.LocationLatitude,
|
_ = os.MkdirAll(filepath.Dir(outPath), 0o755)
|
||||||
"location_longitude": snap.LocationLongitude,
|
tmp := outPath + ".tmp"
|
||||||
"map_zoom_level": snap.MapZoomLevel,
|
_ = os.WriteFile(tmp, b, 0o644)
|
||||||
"map_style": snap.MapStyle,
|
_ = os.Rename(tmp, outPath)
|
||||||
"show_map_on_homepage": snap.ShowMapOnHomepage,
|
}(s)
|
||||||
}
|
go services.PrefetchOnce(getPrefetchBaseURL())
|
||||||
b, _ := json.MarshalIndent(resp, "", " ")
|
|
||||||
outPath := filepath.Join("cache", "prefetch", "settings.json")
|
|
||||||
_ = os.MkdirAll(filepath.Dir(outPath), 0o755)
|
|
||||||
tmp := outPath + ".tmp"
|
|
||||||
_ = os.WriteFile(tmp, b, 0o644)
|
|
||||||
_ = os.Rename(tmp, outPath)
|
|
||||||
}(s)
|
|
||||||
go services.PrefetchOnce(baseURL)
|
|
||||||
}
|
|
||||||
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"})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user