2023-12-19 13:56:03 +01:00
|
|
|
package api_v1
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"net/http"
|
|
|
|
|
2023-12-23 18:22:37 +01:00
|
|
|
"git.ar21.de/yolokube/country-geo-locations/pkg/geoloc"
|
2023-12-19 13:56:03 +01:00
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/go-chi/render"
|
|
|
|
)
|
|
|
|
|
|
|
|
func ApiRouter() chi.Router {
|
|
|
|
r := chi.NewRouter()
|
|
|
|
|
|
|
|
r.Use(render.SetContentType(render.ContentTypeJSON))
|
|
|
|
|
|
|
|
r.Get("/ping", func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
w.Write([]byte("pong"))
|
|
|
|
})
|
|
|
|
|
|
|
|
r.Route("/location", func(r chi.Router) {
|
|
|
|
r.Route("/{ipAddress}", func(r chi.Router) {
|
|
|
|
r.Use(searchIPCtx)
|
|
|
|
r.Get("/", searchIP)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func searchIP(w http.ResponseWriter, r *http.Request) {
|
|
|
|
ipinfo := r.Context().Value(keyIPInfo).(*geoloc.IPInfo)
|
|
|
|
|
|
|
|
if err := render.Render(w, r, newIPInfoResponse(ipinfo)); err != nil {
|
|
|
|
render.Render(w, r, errRender(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func searchIPCtx(next http.Handler) http.Handler {
|
|
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
var ipinfo *geoloc.IPInfo
|
|
|
|
var ipnetnum uint
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if ipAddress := chi.URLParam(r, "ipAddress"); ipAddress != "" {
|
|
|
|
ipnetnum, err = geoloc.GetIPInfo(ipAddress)
|
|
|
|
if err != nil {
|
|
|
|
render.Render(w, r, errInvalidRequest(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for {
|
|
|
|
ipinfo, err = geoloc.SearchIPNet(ipnetnum)
|
|
|
|
if err != nil {
|
|
|
|
render.Render(w, r, errNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if ipinfo != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
ipnetnum = ipnetnum - 8
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ctx := context.WithValue(r.Context(), keyIPInfo, ipinfo)
|
|
|
|
next.ServeHTTP(w, r.WithContext(ctx))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
type errResponse struct {
|
|
|
|
Err error `json:"-"`
|
|
|
|
HTTPStatusCode int `json:"-"`
|
|
|
|
|
|
|
|
StatusText string `json:"status"`
|
|
|
|
AppCode int64 `json:"code,omitempty"`
|
|
|
|
ErrorText string `json:"error,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
render.Status(r, e.HTTPStatusCode)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func errInvalidRequest(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
HTTPStatusCode: 400,
|
|
|
|
StatusText: "Invalid request",
|
|
|
|
ErrorText: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func errRender(err error) render.Renderer {
|
|
|
|
return &errResponse{
|
|
|
|
Err: err,
|
|
|
|
HTTPStatusCode: 422,
|
|
|
|
StatusText: "Error rendering response",
|
|
|
|
ErrorText: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var errNotFound = &errResponse{HTTPStatusCode: 404, StatusText: "Resource not found"}
|
|
|
|
|
|
|
|
type key int
|
|
|
|
|
|
|
|
const keyIPInfo key = iota
|
|
|
|
|
|
|
|
type ipInfoResponse struct {
|
|
|
|
*geoloc.IPInfo
|
|
|
|
|
|
|
|
Elapsed int64 `json:"elapsed"`
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ir *ipInfoResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
|
|
|
ir.Elapsed = 10
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func newIPInfoResponse(ipinfo *geoloc.IPInfo) *ipInfoResponse {
|
|
|
|
return &ipInfoResponse{IPInfo: ipinfo}
|
|
|
|
}
|