package services import ( "bytes" "encoding/json" "net/http" "net/url" "os" "strconv" "strings" "time" "fotbal-club/internal/config" "fotbal-club/internal/models" "gorm.io/gorm" ) type erMonitor struct { ID uint `json:"id,omitempty"` Name string `json:"name"` URL string `json:"url"` Enabled bool `json:"enabled"` CheckIntervalSec int `json:"check_interval_sec"` TimeoutMs int `json:"timeout_ms"` ExpectStatusMin int `json:"expect_status_min"` ExpectStatusMax int `json:"expect_status_max"` } type erListResp struct { Items []erMonitor `json:"items"` } func StartErrorReviewAutoRegister(db *gorm.DB) { go func() { defer func() { _ = recover() }() time.Sleep(2 * time.Second) for i := 0; i < 3; i++ { if tryAutoRegister(db) { return } time.Sleep(time.Duration(2+i) * time.Second) } }() } func tryAutoRegister(db *gorm.DB) bool { if db == nil || config.AppConfig == nil { return false } var s models.Settings _ = db.First(&s).Error // Domain is managed via environment only; fallback based on ERROR_LOCAL adminURL := strings.TrimSpace(os.Getenv("ERROR_REVIEW_ADMIN_URL")) if adminURL == "" { errorLocal := false if b, err := strconv.ParseBool(strings.TrimSpace(os.Getenv("ERROR_LOCAL"))); err == nil && b { errorLocal = true } if !errorLocal { if b, err := strconv.ParseBool(strings.TrimSpace(os.Getenv("error_local"))); err == nil && b { errorLocal = true } } if errorLocal { adminURL = "http://127.0.0.1:8083/api/v1/admin" } else { adminURL = "https://error.tdvorak.dev/api/v1/admin" } } // Prefer env token only token := strings.TrimSpace(os.Getenv("ERROR_REVIEW_ADMIN_TOKEN")) if token == "" { return false } // Only register after setup is complete (club name present) if strings.TrimSpace(s.ClubName) == "" { return false } if strings.HasSuffix(adminURL, "/") { adminURL = strings.TrimRight(adminURL, "/") } base := strings.TrimRight(strings.TrimSpace(s.APIBaseURL), "/") if base == "" { base = strings.TrimRight(config.AppConfig.PublicAPIBaseURL, "/") } if base == "" { // Fallback for local dev errorLocal := false if b, err := strconv.ParseBool(strings.TrimSpace(os.Getenv("ERROR_LOCAL"))); err == nil && b { errorLocal = true } if !errorLocal { if b, err := strconv.ParseBool(strings.TrimSpace(os.Getenv("error_local"))); err == nil && b { errorLocal = true } } if errorLocal { base = "http://127.0.0.1:8080/api/v1" } else { return false } } // Ensure API suffix present if !strings.Contains(base, "/api/") { base = strings.TrimRight(base, "/") + "/api/v1" } monURL := base + "/health" disp := "Fotbal Club" if strings.TrimSpace(s.ClubName) != "" { disp = strings.TrimSpace(s.ClubName) } host := "" if u, err := url.Parse(monURL); err == nil { host = u.Hostname() } name := disp if host != "" { name = name + " (" + host + ")" } name = name + " API Health" mon := erMonitor{ Name: name, URL: monURL, Enabled: true, CheckIntervalSec: 60, TimeoutMs: 4000, ExpectStatusMin: 200, ExpectStatusMax: 399, } client := &http.Client{Timeout: 5 * time.Second} req, _ := http.NewRequest(http.MethodGet, adminURL+"/monitors", nil) req.Header.Set("Authorization", "Bearer "+token) req.Header.Set("X-Admin-Token", token) res, err := client.Do(req) if err != nil { return false } defer res.Body.Close() if res.StatusCode != http.StatusOK { return false } var list erListResp if err := json.NewDecoder(res.Body).Decode(&list); err != nil { return false } var existing *erMonitor for i := range list.Items { if strings.TrimSpace(list.Items[i].URL) == monURL { existing = &list.Items[i] break } } b, _ := json.Marshal(mon) if existing == nil { req2, _ := http.NewRequest(http.MethodPost, adminURL+"/monitors", bytes.NewReader(b)) req2.Header.Set("Authorization", "Bearer "+token) req2.Header.Set("Content-Type", "application/json") req2.Header.Set("X-Admin-Token", token) res2, err2 := client.Do(req2) if err2 != nil { return false } defer res2.Body.Close() if res2.StatusCode == http.StatusCreated || res2.StatusCode == http.StatusOK { return true } return false } req3, _ := http.NewRequest(http.MethodPut, adminURL+"/monitors/"+itoa(existing.ID), bytes.NewReader(b)) req3.Header.Set("Authorization", "Bearer "+token) req3.Header.Set("Content-Type", "application/json") req3.Header.Set("X-Admin-Token", token) res3, err3 := client.Do(req3) if err3 != nil { return false } defer res3.Body.Close() if res3.StatusCode == http.StatusOK { return true } return false } func itoa(u uint) string { return strconv.FormatUint(uint64(u), 10) }