diff --git a/README.md b/README.md index 5590a8c..2807f71 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ By default, the library will use an in-memory cache that will be used to reduce ### Persistent -If you need a persistent cache to live outside of your application, [Redis](https://redis.io/) is supported by this library. To have the library cache address proximity using a Redis instance, simply provide a `geofence.RedisOptions` struct to `geofence.Config.RedisOptions`. If `RedisOptions` is configured, the in-memory cache will not be used. +If you need a persistent cache to live outside of your application, [Redis](https://redis.io/) is supported by this library. To have the library cache address proximity using a Redis instance, simply provide a `RedisOptions` struct using the `cache` package to `geofence.Config.RedisOptions`. If `RedisOptions` is configured, the in-memory cache will not be used. > Note: Only Redis 7 is currently supported at the time of this writing. @@ -76,7 +76,7 @@ import ( "time" "github.com/circa10a/go-geofence" - "github.com/go-redis/redis/v9" + geofencecache "github.com/circa10a/go-geofence/cache" ) func main() { @@ -87,7 +87,7 @@ func main() { AllowPrivateIPAddresses: true, CacheTTL: 7 * (24 * time.Hour), // 1 week // Use Redis for caching - RedisOptions: &geofence.RedisOptions{ + RedisOptions: &geofencecache.RedisOptions{ Addr: "localhost:6379", Password: "", // no password set DB: 0, // use default DB @@ -100,6 +100,7 @@ func main() { if err != nil { log.Fatal(err) } + // Address nearby: false fmt.Println("Address nearby: ", isAddressNearby) } diff --git a/cache/memory.go b/cache/memory.go index 62f25bc..62d9c6a 100644 --- a/cache/memory.go +++ b/cache/memory.go @@ -11,16 +11,18 @@ const ( deleteExpiredCacheItemsInternal = 10 * time.Minute ) +// MemoryCache is used to store/fetch ip proximity from an in-memory cache. type MemoryCache struct { memoryClient *gocache.Cache memoryOptions *MemoryOptions } -// MemoryOptions holds in memory cache configuration parameters. +// MemoryOptions holds in-memory cache configuration parameters. type MemoryOptions struct { TTL time.Duration } +// NewRedisCache provides a new in-memory cache client. func NewMemoryCache(memoryOptions *MemoryOptions) *MemoryCache { return &MemoryCache{ memoryClient: gocache.New(memoryOptions.TTL, deleteExpiredCacheItemsInternal), @@ -28,14 +30,15 @@ func NewMemoryCache(memoryOptions *MemoryOptions) *MemoryCache { } } +// Get gets value from the in-memory cache. func (m *MemoryCache) Get(ctx context.Context, key string) (bool, bool, error) { if isIPAddressNear, found := m.memoryClient.Get(key); found { return isIPAddressNear.(bool), found, nil - } else { - return false, false, nil } + return false, false, nil } +// Set sets k/v in the in-memory cache. func (m *MemoryCache) Set(ctx context.Context, key string, value bool) error { m.memoryClient.Set(key, value, m.memoryOptions.TTL) return nil diff --git a/cache/redis.go b/cache/redis.go index eef324e..e276e91 100644 --- a/cache/redis.go +++ b/cache/redis.go @@ -8,6 +8,7 @@ import ( "github.com/redis/go-redis/v9" ) +// RedisCache is used to store/fetch ip proximity from redis. type RedisCache struct { redisClient *redis.Client redisOptions *RedisOptions @@ -21,6 +22,7 @@ type RedisOptions struct { TTL time.Duration } +// NewRedisCache provides a new redis cache client. func NewRedisCache(redisOpts *RedisOptions) *RedisCache { return &RedisCache{ redisClient: redis.NewClient(&redis.Options{ @@ -32,6 +34,7 @@ func NewRedisCache(redisOpts *RedisOptions) *RedisCache { } } +// Get gets value from redis. func (r *RedisCache) Get(ctx context.Context, key string) (bool, bool, error) { val, err := r.redisClient.Get(ctx, key).Result() if err != nil { @@ -49,7 +52,8 @@ func (r *RedisCache) Get(ctx context.Context, key string) (bool, bool, error) { return isIPAddressNear, true, nil } +// Set sets k/v in redis. func (r *RedisCache) Set(ctx context.Context, key string, value bool) error { - // Redis stores false as 0 for whatever reason, so we'll store as a string and parse out in cacheGet + // Redis stores false as 0 for whatever reason, so we'll store as a string and parse it out return r.redisClient.Set(ctx, key, strconv.FormatBool(value), r.redisOptions.TTL).Err() } diff --git a/geofence.go b/geofence.go index 4aeabd1..d715a7f 100644 --- a/geofence.go +++ b/geofence.go @@ -35,91 +35,11 @@ type Geofence struct { Longitude float64 } -// ipBaseResponse is the json response from ipbase.com +// ipbaseResponse is the json response from ipbase.com type ipbaseResponse struct { Data data `json:"data"` } -type timezone struct { - Id string `json:"id"` - CurrentTime string `json:"current_time"` - Code string `json:"code"` - IDaylightSaving bool `json:"is_daylight_saving"` - GmtOffset int `json:"gmt_offset"` -} - -type connection struct { - Organization string `json:"organization"` - Isp string `json:"isp"` - Asn int `json:"asn"` -} - -type continent struct { - Code string `json:"code"` - Name string `json:"name"` - NameTranslated string `json:"name_translated"` -} - -type currencies struct { - Symbol string `json:"symbol"` - Name string `json:"name"` - SymbolNative string `json:"symbol_native"` - Code string `json:"code"` - NamePlural string `json:"name_plural"` - DecimalDigits int `json:"decimal_digits"` - Rounding int `json:"rounding"` -} - -type languages struct { - Name string `json:"name"` - NameNative string `json:"name_native"` -} - -type country struct { - Alpha2 string `json:"alpha2"` - Alpha3 string `json:"alpha3"` - CallingCodes []string `json:"calling_codes"` - Currencies []currencies `json:"currencies"` - Emoji string `json:"emoji"` - Ioc string `json:"ioc"` - Languages []languages `json:"languages"` - Name string `json:"name"` - NameTranslated string `json:"name_translated"` - Timezones []string `json:"timezones"` - IsInEuropeanUnion bool `json:"is_in_european_union"` -} - -type city struct { - Name string `json:"name"` - NameTranslated string `json:"name_translated"` -} - -type region struct { - Fips interface{} `json:"fips"` - Alpha2 interface{} `json:"alpha2"` - Name string `json:"name"` - NameTranslated string `json:"name_translated"` -} - -type location struct { - GeonamesID interface{} `json:"geonames_id"` - Region region `json:"region"` - Continent continent `json:"continent"` - City city `json:"city"` - Zip string `json:"zip"` - Country country `json:"country"` - Latitude float64 `json:"latitude"` - Longitude float64 `json:"longitude"` -} - -type data struct { - Timezone timezone `json:"timezone"` - IP string `json:"ip"` - Type string `json:"type"` - Connection connection `json:"connection"` - Location location `json:"location"` -} - // IPBaseError is the json response when there is an error from ipbase.com type IPBaseError struct { Message string `json:"message"` diff --git a/geofence_test.go b/geofence_test.go index fb2c998..9c78bc0 100644 --- a/geofence_test.go +++ b/geofence_test.go @@ -70,7 +70,7 @@ func TestGeofenceNear(t *testing.T) { httpmock.ActivateNonDefault(geofence.ipbaseClient.GetClient()) defer httpmock.DeactivateAndReset() - // mock json rsponse + // mock json response response := &ipbaseResponse{ Data: data{ IP: fakeIPAddress, @@ -83,7 +83,7 @@ func TestGeofenceNear(t *testing.T) { }, }, Timezone: timezone{ - Id: "America/Chicago", + ID: "America/Chicago", }, }, } @@ -146,7 +146,7 @@ func TestGeofencePrivateIP(t *testing.T) { }, }, Timezone: timezone{ - Id: "America/Chicago", + ID: "America/Chicago", }, }, } @@ -208,7 +208,7 @@ func TestGeofenceNotNear(t *testing.T) { }, }, Timezone: timezone{ - Id: "America/Chicago", + ID: "America/Chicago", }, }, } diff --git a/go.mod b/go.mod index 98da5e0..7756c09 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/circa10a/go-geofence -go 1.17 +go 1.18 require ( github.com/EpicStep/go-simple-geo/v2 v2.0.1 diff --git a/types.go b/types.go new file mode 100644 index 0000000..97f3e92 --- /dev/null +++ b/types.go @@ -0,0 +1,129 @@ +// Look. +// I know people hate types.go and want to keep the structs +// but damn these are some exaustive types and it flooded the primary package logic + +package geofence + +type rangeType struct { + Type string `json:"type"` + Description string `json:"description"` +} + +type connection struct { + Organization string `json:"organization"` + Isp string `json:"isp"` + Range string `json:"range"` + Asn int `json:"asn"` +} + +type continent struct { + Name string `json:"name"` + NameTranslated string `json:"name_translated"` + WikidataID string `json:"wikidata_id"` + Code int `json:"code"` + GeonamesID int `json:"geonames_id"` +} + +type currencies struct { + Symbol string `json:"symbol"` + Name string `json:"name"` + SymbolNative string `json:"symbol_native"` + Code string `json:"code"` + NamePlural string `json:"name_plural"` + DecimalDigits int `json:"decimal_digits"` + Rounding int `json:"rounding"` +} + +type languages struct { + Name string `json:"name"` + NameNative string `json:"name_native"` +} + +type country struct { + Fips string `json:"fips"` + Alpha3 string `json:"alpha3"` + WikidataID string `json:"wikidata_id"` + HascID string `json:"hasc_id"` + Emoji string `json:"emoji"` + Ioc string `json:"ioc"` + Alpha2 string `json:"alpha2"` + Name string `json:"name"` + NameTranslated string `json:"name_translated"` + Languages []languages `json:"languages"` + Timezones []string `json:"timezones"` + Currencies []currencies `json:"currencies"` + CallingCodes []string `json:"calling_codes"` + GeonamesID int `json:"geonames_id"` + IsInEuropeanUnion bool `json:"is_in_european_union"` +} + +type city struct { + Alpha2 any `json:"alpha2"` + HascID any `json:"hasc_id"` + Fips string `json:"fips"` + WikidataID string `json:"wikidata_id"` + Name string `json:"name"` + NameTranslated string `json:"name_translated"` + GeonamesID int `json:"geonames_id"` +} + +type region struct { + Fips string `json:"fips"` + Alpha2 string `json:"alpha2"` + HascID string `json:"hasc_id"` + WikidataID string `json:"wikidata_id"` + Name string `json:"name"` + NameTranslated string `json:"name_translated"` + GeonamesID int `json:"geonames_id"` +} + +type location struct { + Country country `json:"country"` + City city `json:"city"` + Region region `json:"region"` + Continent continent `json:"continent"` + Zip string `json:"zip"` + GeonamesID int `json:"geonames_id"` + Latitude float64 `json:"latitude"` + Longitude float64 `json:"longitude"` +} + +type timezone struct { + ID string `json:"id"` + CurrentTime string `json:"current_time"` + Code string `json:"code"` + IsDaylightSaving bool `json:"is_daylight_saving"` + GmtOffset int `json:"gmt_offset"` +} + +type security struct { + IsAnonymous bool `json:"is_anonymous"` + IsDatacenter bool `json:"is_datacenter"` + IsVpn bool `json:"is_vpn"` + IsBot bool `json:"is_bot"` + IsAbuser bool `json:"is_abuser"` + IsKnownAttacker bool `json:"is_known_attacker"` + IsProxy bool `json:"is_proxy"` + IsSpam bool `json:"is_spam"` + IsTor bool `json:"is_tor"` + IsIcloudRelay bool `json:"is_icloud_relay"` + ThreatScore int `json:"threat_score"` +} + +type domains struct { + Domains []string `json:"domains"` + Count int `json:"count"` +} + +type data struct { + Location location `json:"location"` + Connection connection `json:"connection"` + RangeType rangeType `json:"range_type"` + IP string `json:"ip"` + Hostname string `json:"hostname"` + Type string `json:"type"` + Domains domains `json:"domains"` + Tlds []string `json:"tlds"` + Timezone timezone `json:"timezone"` + Security security `json:"security"` +}