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)
|
||||
GetLiveRouteCounts() (ipv4Count, ipv6Count int, err error)
|
||||
|
||||
// IP lookup operations
|
||||
GetASInfoForIP(ip string) (*ASInfo, error)
|
||||
|
||||
// Lifecycle
|
||||
Close() error
|
||||
}
|
||||
|
@ -187,6 +187,17 @@ func (m *mockStore) GetLiveRouteCounts() (ipv4Count, ipv6Count int, err error) {
|
||||
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) {
|
||||
|
||||
// Create mock database
|
||||
|
@ -61,6 +61,7 @@ func (s *Server) setupRoutes() {
|
||||
// API routes
|
||||
r.Route("/api/v1", func(r chi.Router) {
|
||||
r.Get("/stats", s.handleStats())
|
||||
r.Get("/ip/{ip}", s.handleIPLookup())
|
||||
})
|
||||
|
||||
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
|
||||
func (s *Server) handleStatusJSON() http.HandlerFunc {
|
||||
// Stats represents the statistics response
|
||||
@ -164,28 +188,12 @@ func (s *Server) handleStatusJSON() http.HandlerFunc {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.logger.Error("Database stats timeout in status.json")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusRequestTimeout)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "error",
|
||||
"error": map[string]interface{}{
|
||||
"msg": "Database timeout",
|
||||
"code": http.StatusRequestTimeout,
|
||||
},
|
||||
})
|
||||
writeJSONError(w, http.StatusRequestTimeout, "Database timeout")
|
||||
|
||||
return
|
||||
case err := <-errChan:
|
||||
s.logger.Error("Failed to get database stats", "error", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "error",
|
||||
"error": map[string]interface{}{
|
||||
"msg": err.Error(),
|
||||
"code": http.StatusInternalServerError,
|
||||
},
|
||||
})
|
||||
writeJSONError(w, http.StatusInternalServerError, err.Error())
|
||||
|
||||
return
|
||||
case dbStats = <-statsChan:
|
||||
@ -239,12 +247,7 @@ func (s *Server) handleStatusJSON() http.HandlerFunc {
|
||||
IPv6PrefixDistribution: dbStats.IPv6PrefixDistribution,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := map[string]interface{}{
|
||||
"status": "ok",
|
||||
"data": stats,
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
if err := writeJSONSuccess(w, stats); err != nil {
|
||||
s.logger.Error("Failed to encode stats", "error", err)
|
||||
}
|
||||
}
|
||||
@ -404,12 +407,7 @@ func (s *Server) handleStats() http.HandlerFunc {
|
||||
IPv6PrefixDistribution: dbStats.IPv6PrefixDistribution,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
response := map[string]interface{}{
|
||||
"status": "ok",
|
||||
"data": stats,
|
||||
}
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
if err := writeJSONSuccess(w, stats); err != nil {
|
||||
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