Add /api/v1/ip/<ip> endpoint for IP to AS lookups
- Add handleIPLookup handler that uses GetASInfoForIP - Create writeJSONError and writeJSONSuccess helper functions - Refactor all JSON error responses to use the helpers - Add GetASInfoForIP to Store interface - Add mock implementation for tests - Fix all linter warnings
This commit is contained in:
parent
afb916036c
commit
691710bc7c
@ -44,6 +44,9 @@ type Store interface {
|
|||||||
GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []PrefixDistribution, err error)
|
GetPrefixDistribution() (ipv4 []PrefixDistribution, ipv6 []PrefixDistribution, err error)
|
||||||
GetLiveRouteCounts() (ipv4Count, ipv6Count int, err error)
|
GetLiveRouteCounts() (ipv4Count, ipv6Count int, err error)
|
||||||
|
|
||||||
|
// IP lookup operations
|
||||||
|
GetASInfoForIP(ip string) (*ASInfo, error)
|
||||||
|
|
||||||
// Lifecycle
|
// Lifecycle
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
@ -187,6 +187,17 @@ func (m *mockStore) GetLiveRouteCounts() (ipv4Count, ipv6Count int, err error) {
|
|||||||
return m.RouteCount / 2, m.RouteCount / 2, nil
|
return m.RouteCount / 2, m.RouteCount / 2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetASInfoForIP mock implementation
|
||||||
|
func (m *mockStore) GetASInfoForIP(ip string) (*database.ASInfo, error) {
|
||||||
|
// Simple mock - return a test AS
|
||||||
|
return &database.ASInfo{
|
||||||
|
ASN: 15169,
|
||||||
|
Handle: "GOOGLE",
|
||||||
|
Description: "Google LLC",
|
||||||
|
Prefix: "8.8.8.0/24",
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func TestRouteWatchLiveFeed(t *testing.T) {
|
func TestRouteWatchLiveFeed(t *testing.T) {
|
||||||
|
|
||||||
// Create mock database
|
// Create mock database
|
||||||
|
@ -61,6 +61,7 @@ func (s *Server) setupRoutes() {
|
|||||||
// API routes
|
// API routes
|
||||||
r.Route("/api/v1", func(r chi.Router) {
|
r.Route("/api/v1", func(r chi.Router) {
|
||||||
r.Get("/stats", s.handleStats())
|
r.Get("/stats", s.handleStats())
|
||||||
|
r.Get("/ip/{ip}", s.handleIPLookup())
|
||||||
})
|
})
|
||||||
|
|
||||||
s.router = r
|
s.router = r
|
||||||
@ -109,6 +110,29 @@ func (s *Server) handleRoot() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// writeJSONError writes a standardized JSON error response
|
||||||
|
func writeJSONError(w http.ResponseWriter, statusCode int, message string) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(statusCode)
|
||||||
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"status": "error",
|
||||||
|
"error": map[string]interface{}{
|
||||||
|
"msg": message,
|
||||||
|
"code": statusCode,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeJSONSuccess writes a standardized JSON success response
|
||||||
|
func writeJSONSuccess(w http.ResponseWriter, data interface{}) error {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
return json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
|
"status": "ok",
|
||||||
|
"data": data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// handleStatusJSON returns a handler that serves JSON statistics
|
// handleStatusJSON returns a handler that serves JSON statistics
|
||||||
func (s *Server) handleStatusJSON() http.HandlerFunc {
|
func (s *Server) handleStatusJSON() http.HandlerFunc {
|
||||||
// Stats represents the statistics response
|
// Stats represents the statistics response
|
||||||
@ -164,28 +188,12 @@ func (s *Server) handleStatusJSON() http.HandlerFunc {
|
|||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
s.logger.Error("Database stats timeout in status.json")
|
s.logger.Error("Database stats timeout in status.json")
|
||||||
w.Header().Set("Content-Type", "application/json")
|
writeJSONError(w, http.StatusRequestTimeout, "Database timeout")
|
||||||
w.WriteHeader(http.StatusRequestTimeout)
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"status": "error",
|
|
||||||
"error": map[string]interface{}{
|
|
||||||
"msg": "Database timeout",
|
|
||||||
"code": http.StatusRequestTimeout,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
return
|
||||||
case err := <-errChan:
|
case err := <-errChan:
|
||||||
s.logger.Error("Failed to get database stats", "error", err)
|
s.logger.Error("Failed to get database stats", "error", err)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
|
||||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
||||||
"status": "error",
|
|
||||||
"error": map[string]interface{}{
|
|
||||||
"msg": err.Error(),
|
|
||||||
"code": http.StatusInternalServerError,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
return
|
return
|
||||||
case dbStats = <-statsChan:
|
case dbStats = <-statsChan:
|
||||||
@ -239,12 +247,7 @@ func (s *Server) handleStatusJSON() http.HandlerFunc {
|
|||||||
IPv6PrefixDistribution: dbStats.IPv6PrefixDistribution,
|
IPv6PrefixDistribution: dbStats.IPv6PrefixDistribution,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
if err := writeJSONSuccess(w, stats); err != nil {
|
||||||
response := map[string]interface{}{
|
|
||||||
"status": "ok",
|
|
||||||
"data": stats,
|
|
||||||
}
|
|
||||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
||||||
s.logger.Error("Failed to encode stats", "error", err)
|
s.logger.Error("Failed to encode stats", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,12 +407,7 @@ func (s *Server) handleStats() http.HandlerFunc {
|
|||||||
IPv6PrefixDistribution: dbStats.IPv6PrefixDistribution,
|
IPv6PrefixDistribution: dbStats.IPv6PrefixDistribution,
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
if err := writeJSONSuccess(w, stats); err != nil {
|
||||||
response := map[string]interface{}{
|
|
||||||
"status": "ok",
|
|
||||||
"data": stats,
|
|
||||||
}
|
|
||||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
|
||||||
s.logger.Error("Failed to encode stats", "error", err)
|
s.logger.Error("Failed to encode stats", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -427,3 +425,28 @@ func (s *Server) handleStatusHTML() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleIPLookup returns a handler that looks up AS information for an IP address
|
||||||
|
func (s *Server) handleIPLookup() http.HandlerFunc {
|
||||||
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
ip := chi.URLParam(r, "ip")
|
||||||
|
if ip == "" {
|
||||||
|
writeJSONError(w, http.StatusBadRequest, "IP parameter is required")
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Look up AS information for the IP
|
||||||
|
asInfo, err := s.db.GetASInfoForIP(ip)
|
||||||
|
if err != nil {
|
||||||
|
writeJSONError(w, http.StatusNotFound, err.Error())
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return successful response
|
||||||
|
if err := writeJSONSuccess(w, asInfo); err != nil {
|
||||||
|
s.logger.Error("Failed to encode AS info", "error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user