This repository has been archived on 2025-11-19. You can view files and clone it, but cannot push or open issues or pull requests.
Skyfeed_archive/internal/geo/towns_updater.go

124 lines
3.4 KiB
Go

package geo
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"time"
"git.leaktechnologies.dev/Leak_Technologies/Skyfeed/internal/config"
)
const (
// WORKING GeoGratis endpoint for all populated places (PPL)
gnbcAPIURL = "https://geogratis.gc.ca/services/geoname/en/geonames.json?feature_code=PPL"
townsFile = "towns.json"
maxFetchTime = 5 * time.Minute
)
// EnsureTownIndexUpToDate checks if the towns index needs updating (monthly).
func EnsureTownIndexUpToDate() error {
dest := filepath.Join(config.DataDir, townsFile)
info, err := os.Stat(dest)
if os.IsNotExist(err) {
fmt.Println("[geo] No town index found, downloading...")
return downloadTownIndex(dest)
}
if err != nil {
return fmt.Errorf("unable to check town index: %w", err)
}
modTime := info.ModTime().UTC()
now := time.Now().UTC()
firstOfMonth := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, time.UTC)
if modTime.Before(firstOfMonth) {
fmt.Println("[geo] Town index is older than this month, refreshing...")
return downloadTownIndex(dest)
}
fmt.Println("[geo] Town index is current.")
return nil
}
// ForceUpdateTownIndex forces an immediate rebuild.
func ForceUpdateTownIndex() error {
dest := filepath.Join(config.DataDir, townsFile)
fmt.Println("[geo] Forcing town index update...")
return downloadTownIndex(dest)
}
func downloadTownIndex(dest string) error {
client := &http.Client{Timeout: maxFetchTime}
fmt.Println("[geo] Fetching town data from GeoGratis...")
resp, err := client.Get(gnbcAPIURL)
if err != nil {
return fmt.Errorf("failed to fetch town dataset: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("unexpected HTTP status: %s", resp.Status)
}
raw, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read GeoGratis response: %w", err)
}
towns, err := parseGNBCJSON(raw)
if err != nil {
return fmt.Errorf("failed to parse GeoGratis JSON: %w", err)
}
data, err := json.MarshalIndent(towns, "", " ")
if err != nil {
return fmt.Errorf("failed to encode towns.json: %w", err)
}
if err := os.WriteFile(dest, data, 0644); err != nil {
return fmt.Errorf("failed to write %s: %w", dest, err)
}
fmt.Printf("[geo] Town index updated → %s (%d towns)\n", dest, len(towns))
return nil
}
// Uses GeoGratis geonames.json structure
func parseGNBCJSON(data []byte) ([]Town, error) {
var response struct {
Items []struct {
Name string `json:"name"`
ProvinceCode string `json:"province_code"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
} `json:"items"`
}
if err := json.Unmarshal(data, &response); err != nil {
return nil, fmt.Errorf("invalid GeoGratis JSON: %w", err)
}
var towns []Town
for _, item := range response.Items {
if item.Name == "" || item.ProvinceCode == "" {
continue
}
towns = append(towns, Town{
Name: item.Name,
Province: item.ProvinceCode,
Lat: item.Latitude,
Lon: item.Longitude,
})
}
return towns, nil
}