-
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ feat: add enhanced analytics with user agent and geolocation tracking
- Loading branch information
Showing
5 changed files
with
165 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package utils | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
) | ||
|
||
type LocationInfo struct { | ||
City string `json:"city"` | ||
Region string `json:"regionName"` | ||
ZipCode string `json:"zip"` | ||
Country string `json:"countryCode"` | ||
} | ||
|
||
// GetLocationInfo fetches the location info for an IP address using ip-api.com | ||
func GetLocationInfo(ipAddress string) LocationInfo { | ||
return GetLocationInfoWithClient(ipAddress, http.DefaultClient) | ||
} | ||
|
||
// GetLocationInfoWithClient fetches the location info using a custom HTTP client | ||
func GetLocationInfoWithClient(ipAddress string, client *http.Client) LocationInfo { | ||
resp, err := client.Get("http://ip-api.com/json/" + ipAddress) | ||
if err != nil { | ||
return LocationInfo{} | ||
} | ||
defer resp.Body.Close() | ||
|
||
var result struct { | ||
City string `json:"city"` | ||
Region string `json:"regionName"` | ||
ZipCode string `json:"zip"` | ||
Country string `json:"countryCode"` | ||
} | ||
|
||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { | ||
return LocationInfo{} | ||
} | ||
|
||
return result | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package utils | ||
|
||
import ( | ||
"io" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
) | ||
|
||
type mockTransport struct { | ||
response string | ||
} | ||
|
||
func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) { | ||
return &http.Response{ | ||
Status: "200 OK", | ||
StatusCode: http.StatusOK, | ||
Body: io.NopCloser(strings.NewReader(t.response)), | ||
}, nil | ||
} | ||
|
||
func TestGetLocationInfo(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
ip string | ||
mock string | ||
expected LocationInfo | ||
}{ | ||
{ | ||
name: "valid ip address", | ||
ip: "136.36.156.245", | ||
mock: `{"status":"success","country":"United States","countryCode":"US","region":"UT","regionName":"Utah","city":"Salt Lake City","zip":"84106","lat":40.6982,"lon":-111.841,"timezone":"America/Denver","isp":"Google Fiber Inc.","org":"Google Fiber Inc","as":"AS16591 Google Fiber Inc.","query":"136.36.156.245"}`, | ||
expected: LocationInfo{ | ||
City: "Salt Lake City", | ||
Region: "Utah", | ||
ZipCode: "84106", | ||
Country: "US", | ||
}, | ||
}, | ||
{ | ||
name: "invalid ip address", | ||
ip: "invalid", | ||
mock: `{"status":"fail","message":"invalid query","query":"invalid"}`, | ||
expected: LocationInfo{ | ||
City: "", | ||
Region: "", | ||
ZipCode: "", | ||
Country: "", | ||
}, | ||
}, | ||
{ | ||
name: "server error", | ||
ip: "error", | ||
mock: `{"error": "internal server error"}`, | ||
expected: LocationInfo{ | ||
City: "", | ||
Region: "", | ||
ZipCode: "", | ||
Country: "", | ||
}, | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create a custom client with our mock transport | ||
client := &http.Client{ | ||
Transport: &mockTransport{response: tt.mock}, | ||
} | ||
|
||
// Create a test server just to get a valid URL | ||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})) | ||
defer server.Close() | ||
|
||
// Call the function being tested | ||
result := GetLocationInfoWithClient(tt.ip, client) | ||
|
||
// Check the results | ||
if result.City != tt.expected.City { | ||
t.Errorf("City = %v, want %v", result.City, tt.expected.City) | ||
} | ||
if result.Region != tt.expected.Region { | ||
t.Errorf("Region = %v, want %v", result.Region, tt.expected.Region) | ||
} | ||
if result.ZipCode != tt.expected.ZipCode { | ||
t.Errorf("ZipCode = %v, want %v", result.ZipCode, tt.expected.ZipCode) | ||
} | ||
if result.Country != tt.expected.Country { | ||
t.Errorf("Country = %v, want %v", result.Country, tt.expected.Country) | ||
} | ||
}) | ||
} | ||
} |