99 lines
1.9 KiB
Go
99 lines
1.9 KiB
Go
package geo
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"errors"
|
|
"net"
|
|
"time"
|
|
|
|
_ "github.com/jackc/pgx/v5/stdlib"
|
|
)
|
|
|
|
const defaultLookupQuery = `
|
|
SELECT
|
|
ip::text,
|
|
country,
|
|
region,
|
|
city,
|
|
latitude,
|
|
longitude
|
|
FROM geoip.lookup_city($1);
|
|
`
|
|
|
|
type postgresResolver struct {
|
|
db *sql.DB
|
|
lookupQuery string
|
|
}
|
|
|
|
func newPostgresResolver(databaseURL, lookupQuery string) (Resolver, error) {
|
|
if databaseURL == "" {
|
|
return nil, errors.New("database url is required for postgres backend")
|
|
}
|
|
|
|
db, err := sql.Open("pgx", databaseURL)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
db.SetMaxOpenConns(10)
|
|
db.SetMaxIdleConns(2)
|
|
db.SetConnMaxIdleTime(5 * time.Minute)
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
if err := db.PingContext(ctx); err != nil {
|
|
_ = db.Close()
|
|
return nil, err
|
|
}
|
|
|
|
if lookupQuery == "" {
|
|
lookupQuery = defaultLookupQuery
|
|
}
|
|
|
|
return &postgresResolver{
|
|
db: db,
|
|
lookupQuery: lookupQuery,
|
|
}, nil
|
|
}
|
|
|
|
func (r *postgresResolver) Close() error {
|
|
return r.db.Close()
|
|
}
|
|
|
|
func (r *postgresResolver) Lookup(ipStr string) (Location, error) {
|
|
ip := net.ParseIP(ipStr)
|
|
if ip == nil {
|
|
return Location{}, ErrInvalidIP
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
defer cancel()
|
|
|
|
row := r.db.QueryRowContext(ctx, r.lookupQuery, ip.String())
|
|
|
|
var (
|
|
resolvedIP string
|
|
country, region sql.NullString
|
|
city sql.NullString
|
|
latitude, longitude sql.NullFloat64
|
|
)
|
|
|
|
if err := row.Scan(&resolvedIP, &country, ®ion, &city, &latitude, &longitude); err != nil {
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
return Location{}, ErrNotFound
|
|
}
|
|
return Location{}, err
|
|
}
|
|
|
|
return Location{
|
|
IP: resolvedIP,
|
|
Country: country.String,
|
|
Region: region.String,
|
|
City: city.String,
|
|
Address: buildAddress(city.String, region.String, country.String),
|
|
Latitude: latitude.Float64,
|
|
Longitude: longitude.Float64,
|
|
}, nil
|
|
}
|