package geo import ( "fmt" "net" "github.com/oschwald/geoip2-golang" ) // GeoPoint holds geographic information for an IP address. type GeoPoint struct { IP string Lat float64 Lon float64 Country string CountryCode string Count int } // GeoLookup wraps the geoip2 database reader. // If no database path is configured, all lookups return nil (no-op). type GeoLookup struct { db *geoip2.Reader } // NewGeoLookup creates a GeoLookup. If path is empty, returns a disabled (no-op) lookup. // Returns an error if path is non-empty but the file cannot be opened. func NewGeoLookup(path string) (*GeoLookup, error) { if path == "" { return &GeoLookup{}, nil } db, err := geoip2.Open(path) if err != nil { return nil, fmt.Errorf("geoip2 open %q: %w", path, err) } return &GeoLookup{db: db}, nil } // IsEnabled returns true if a GeoIP2 database is loaded. func (g *GeoLookup) IsEnabled() bool { return g.db != nil } // Close releases the database if open. func (g *GeoLookup) Close() { if g.db != nil { g.db.Close() } } // Lookup returns a GeoPoint for the given IP, or nil if the database is not loaded // or the IP is nil/not found. func (g *GeoLookup) Lookup(ip net.IP) *GeoPoint { if g.db == nil || ip == nil { return nil } record, err := g.db.City(ip) if err != nil { return nil } return &GeoPoint{ IP: ip.String(), Lat: record.Location.Latitude, Lon: record.Location.Longitude, Country: record.Country.Names["en"], CountryCode: record.Country.IsoCode, } }