package api import ( "errors" "github.com/gin-gonic/gin" "github.com/rs/zerolog/log" "net" "net/http" "thuanle.me/ip-info/internal/data" ) func HandleJson(c *gin.Context) { ip := c.ClientIP() // ClientIP can yield an unparseable value from malformed proxy headers; // reject it explicitly instead of letting Query fail into a 500. if net.ParseIP(ip) == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip", "ip": ip}) return } // selfLookup=true preserves the "default value when lookup internal ip" // contract (commit 9d07639): a valid but DB-absent client IP (loopback / // private addresses) returns 200 {"ip": ...} rather than a 404, since the // caller did not ask for a specific lookup. HandleIpInfo(c, ip, true) } func HandleOtherIp(c *gin.Context) { ip := c.Param("ip") if net.ParseIP(ip) == nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ip", "ip": ip}) return } // The caller explicitly requested this IP, so a not-found is a real 404. HandleIpInfo(c, ip, false) } func HandleIpInfo(c *gin.Context, ip string, selfLookup bool) { if !data.Ins().IsLoaded() { log.Error().Msg("DB is not loaded") c.JSON(http.StatusServiceUnavailable, gin.H{ "error": "service unavailable, db still loading", "ip": ip, }) return } ipData, _, err := data.Ins().Query(ip) if err != nil { // Distinguish "not found" (valid IP, absent from DB) from other errors. if isNotFound(err) { if selfLookup { // Graceful default for the self-IP (/json) path: the client's // address is valid but simply absent from the public GeoIP DB. c.JSON(http.StatusOK, gin.H{"ip": ip}) return } c.JSON(http.StatusNotFound, gin.H{"error": "not found", "ip": ip}) return } log.Err(err).Msg("Failed to query IP") c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error", "ip": ip}) return } ipData["ip"] = ip c.JSON(http.StatusOK, ipData) } // isNotFound reports whether the query error means the IP was not present in // the DB (as opposed to an invalid IP or an internal lookup failure). func isNotFound(err error) bool { return errors.Is(err, data.ErrNotFound) }