initial commit
This commit is contained in:
commit
885e7d6e02
22 changed files with 3001 additions and 0 deletions
68
internal/geo/geo.go
Normal file
68
internal/geo/geo.go
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
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,
|
||||
}
|
||||
}
|
||||
59
internal/geo/geo_test.go
Normal file
59
internal/geo/geo_test.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
package geo
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNewGeoLookup_NilPath(t *testing.T) {
|
||||
gl, err := NewGeoLookup("")
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if gl == nil {
|
||||
t.Fatal("expected non-nil GeoLookup")
|
||||
}
|
||||
if gl.IsEnabled() {
|
||||
t.Error("expected IsEnabled() = false for empty path")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoLookup_LookupWithNoDb(t *testing.T) {
|
||||
gl, _ := NewGeoLookup("")
|
||||
result := gl.Lookup(net.ParseIP("8.8.8.8"))
|
||||
if result != nil {
|
||||
t.Errorf("expected nil result when no db, got %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoLookup_LookupNilIP(t *testing.T) {
|
||||
gl, _ := NewGeoLookup("")
|
||||
result := gl.Lookup(nil)
|
||||
if result != nil {
|
||||
t.Errorf("expected nil result for nil IP, got %+v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewGeoLookup_InvalidPath(t *testing.T) {
|
||||
_, err := NewGeoLookup("/nonexistent/path/GeoLite2-City.mmdb")
|
||||
if err == nil {
|
||||
t.Error("expected error for nonexistent db file, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGeoPoint_Fields(t *testing.T) {
|
||||
gp := GeoPoint{
|
||||
IP: "1.2.3.4",
|
||||
Lat: 37.751,
|
||||
Lon: -97.822,
|
||||
Country: "United States",
|
||||
CountryCode: "US",
|
||||
Count: 42,
|
||||
}
|
||||
if gp.IP != "1.2.3.4" {
|
||||
t.Errorf("IP = %q", gp.IP)
|
||||
}
|
||||
if gp.Count != 42 {
|
||||
t.Errorf("Count = %d", gp.Count)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue