mirror of
https://github.com/Dvorinka/MyClubServer.git
synced 2026-06-03 18:22:57 +00:00
159 lines
5.8 KiB
Go
159 lines
5.8 KiB
Go
package controllers
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"fotbal-club/internal/services"
|
|
)
|
|
|
|
// PrefetchController exposes admin endpoints to inspect and trigger background prefetch cycles.
|
|
type PrefetchController struct{}
|
|
|
|
// isDuringMatchFromCache checks cached FACR JSONs to determine if a match is ongoing.
|
|
func isDuringMatchFromCache(cacheDir string) bool {
|
|
now := time.Now()
|
|
parseDT := func(s string) (time.Time, bool) {
|
|
s = strings.TrimSpace(s)
|
|
if s == "" { return time.Time{}, false }
|
|
layouts := []string{"02.01.2006 15:04", time.RFC3339, "2006-01-02 15:04"}
|
|
for _, layout := range layouts {
|
|
if t, err := time.ParseInLocation(layout, s, time.Local); err == nil { return t, true }
|
|
}
|
|
return time.Time{}, false
|
|
}
|
|
within := func(ts time.Time) bool {
|
|
start := ts.Add(-15 * time.Minute)
|
|
end := ts.Add(150 * time.Minute)
|
|
return now.After(start) && now.Before(end)
|
|
}
|
|
type flatMatch struct {
|
|
DateTime string `json:"date_time"`
|
|
Date string `json:"date"`
|
|
Time string `json:"time"`
|
|
Kickoff string `json:"kickoff"`
|
|
}
|
|
check := func(b []byte) bool {
|
|
if len(b) == 0 { return false }
|
|
var payload struct {
|
|
Competitions []struct { Matches []struct {
|
|
DateTime string `json:"date_time"`
|
|
Date string `json:"date"`
|
|
Time string `json:"time"`
|
|
Kickoff string `json:"kickoff"`
|
|
} `json:"matches"` } `json:"competitions"`
|
|
Matches []flatMatch `json:"matches"`
|
|
}
|
|
_ = json.Unmarshal(b, &payload)
|
|
for _, c := range payload.Competitions {
|
|
for _, m := range c.Matches {
|
|
var ts time.Time; var ok bool
|
|
if m.DateTime != "" { ts, ok = parseDT(m.DateTime) } else if m.Date != "" || m.Time != "" { ts, ok = parseDT(strings.TrimSpace(m.Date+" "+m.Time)) } else if m.Kickoff != "" { ts, ok = parseDT(m.Kickoff) }
|
|
if ok && within(ts) { return true }
|
|
}
|
|
}
|
|
for _, m := range payload.Matches {
|
|
var ts time.Time; var ok bool
|
|
if m.DateTime != "" { ts, ok = parseDT(m.DateTime) } else if m.Date != "" || m.Time != "" { ts, ok = parseDT(strings.TrimSpace(m.Date+" "+m.Time)) } else if m.Kickoff != "" { ts, ok = parseDT(m.Kickoff) }
|
|
if ok && within(ts) { return true }
|
|
}
|
|
return false
|
|
}
|
|
files := []string{ filepath.Join(cacheDir, "facr_club_info.json"), filepath.Join(cacheDir, "facr_tables.json") }
|
|
for _, p := range files {
|
|
if b, err := os.ReadFile(p); err == nil {
|
|
if check(b) { return true }
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func NewPrefetchController() *PrefetchController { return &PrefetchController{} }
|
|
|
|
// Status returns info about the last fetch and the approximate next run time.
|
|
// GET /api/v1/admin/prefetch/status
|
|
func (pc *PrefetchController) Status(c *gin.Context) {
|
|
if c.GetString("userRole") != "admin" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
|
|
return
|
|
}
|
|
|
|
cacheDir := filepath.Join("cache", "prefetch")
|
|
metaPath := filepath.Join(cacheDir, "meta.json")
|
|
var lastUpdated string
|
|
if b, err := os.ReadFile(metaPath); err == nil {
|
|
var m struct{ LastUpdated string `json:"lastUpdated"` }
|
|
if json.Unmarshal(b, &m) == nil {
|
|
lastUpdated = m.LastUpdated
|
|
}
|
|
}
|
|
|
|
// Determine configured interval
|
|
interval := 30 * time.Minute
|
|
if v := strings.TrimSpace(os.Getenv("PREFETCH_INTERVAL_MINUTES")); v != "" {
|
|
if mins, err := strconv.Atoi(v); err == nil && mins > 0 {
|
|
interval = time.Duration(mins) * time.Minute
|
|
}
|
|
}
|
|
|
|
// Fast mode if during match
|
|
fast := isDuringMatchFromCache(cacheDir)
|
|
next := time.Now().Add(interval)
|
|
if fast {
|
|
next = time.Now().Add(5 * time.Minute)
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"lastUpdated": lastUpdated,
|
|
"intervalMinutes": int(interval / time.Minute),
|
|
"fastMode": fast,
|
|
"nextApproximate": next.Format(time.RFC3339),
|
|
})
|
|
}
|
|
|
|
// Trigger starts an immediate prefetch cycle (best effort)
|
|
// POST /api/v1/admin/prefetch/trigger
|
|
func (pc *PrefetchController) Trigger(c *gin.Context) {
|
|
if c.GetString("userRole") != "admin" {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
|
|
return
|
|
}
|
|
// Resolve the base URL similar to main.go logic
|
|
base := os.Getenv("PREFETCH_TARGET")
|
|
if strings.TrimSpace(base) == "" {
|
|
port := strings.TrimSpace(os.Getenv("PORT"))
|
|
if port == "" { port = "8080" }
|
|
base = "http://127.0.0.1:" + port + "/api/v1"
|
|
}
|
|
go func() {
|
|
// fire-and-forget
|
|
services.PrefetchOnce(base)
|
|
// Additionally trigger an immediate Zonerama refresh based on cached settings
|
|
// This ensures the admin trigger updates the gallery right away.
|
|
cacheDir := filepath.Join("cache", "prefetch")
|
|
b, err := os.ReadFile(filepath.Join(cacheDir, "settings.json"))
|
|
if err == nil {
|
|
var s struct{
|
|
ZoneramaURL string `json:"zonerama_url"`
|
|
GalleryURL string `json:"gallery_url"`
|
|
}
|
|
if jsonErr := json.Unmarshal(b, &s); jsonErr == nil {
|
|
link := strings.TrimSpace(s.ZoneramaURL)
|
|
if link == "" { link = strings.TrimSpace(s.GalleryURL) }
|
|
if link != "" {
|
|
_ = services.RefreshZoneramaNow(link)
|
|
}
|
|
}
|
|
}
|
|
// Regenerate flat gallery files from existing albums
|
|
_ = services.RegenerateFlatGalleryFiles()
|
|
}()
|
|
c.JSON(http.StatusOK, gin.H{"message": "Prefetch started"})
|
|
}
|