package sync import ( "encoding/json" "fmt" "io" "net/http" "time" "vpnem/internal/models" ) // Fetcher pulls configuration from the vpnem server API. type Fetcher struct { baseURL string client *http.Client } // NewFetcher creates a new Fetcher. func NewFetcher(baseURL string) *Fetcher { return &Fetcher{ baseURL: baseURL, client: &http.Client{ Timeout: 15 * time.Second, }, } } // FetchServers retrieves the server list from the API. func (f *Fetcher) FetchServers() (*models.ServersResponse, error) { var resp models.ServersResponse if err := f.getJSON("/api/v1/servers", &resp); err != nil { return nil, fmt.Errorf("fetch servers: %w", err) } return &resp, nil } // FetchRuleSets retrieves the rule-set manifest from the API. func (f *Fetcher) FetchRuleSets() (*models.RuleSetManifest, error) { var resp models.RuleSetManifest if err := f.getJSON("/api/v1/ruleset/manifest", &resp); err != nil { return nil, fmt.Errorf("fetch rulesets: %w", err) } return &resp, nil } // FetchVersion retrieves the latest client version info. func (f *Fetcher) FetchVersion() (*models.VersionResponse, error) { var resp models.VersionResponse if err := f.getJSON("/api/v1/version", &resp); err != nil { return nil, fmt.Errorf("fetch version: %w", err) } return &resp, nil } // ServerIPs extracts all unique server IPs from the server list. func ServerIPs(servers []models.Server) []string { seen := make(map[string]bool) var ips []string for _, s := range servers { if !seen[s.Server] { seen[s.Server] = true ips = append(ips, s.Server) } } return ips } func (f *Fetcher) getJSON(path string, v any) error { resp, err := f.client.Get(f.baseURL + path) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { body, _ := io.ReadAll(resp.Body) return fmt.Errorf("HTTP %d: %s", resp.StatusCode, string(body)) } return json.NewDecoder(resp.Body).Decode(v) }