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:
2025-07-28 03:31:53 +02:00
parent afb916036c
commit 691710bc7c
3 changed files with 67 additions and 30 deletions

View File

@@ -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)
}
}
}