refactor: restructure project
This commit is contained in:
parent
1bde2041e1
commit
8215e1f13a
21 changed files with 850 additions and 269 deletions
65
api/v1/handler.go
Normal file
65
api/v1/handler.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"git.ar21.de/yolokube/country-geo-locations/internal/cache"
|
||||
"git.ar21.de/yolokube/country-geo-locations/internal/database"
|
||||
"git.ar21.de/yolokube/country-geo-locations/pkg/geoloc"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type LocationHandler struct {
|
||||
cache *cache.Cache
|
||||
db *database.Database
|
||||
}
|
||||
|
||||
func NewLocationHandler(cache *cache.Cache, db *database.Database) *LocationHandler {
|
||||
return &LocationHandler{
|
||||
cache: cache,
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (lh *LocationHandler) SearchIPHandlerFunc(w http.ResponseWriter, r *http.Request) {
|
||||
ipinfo, ok := r.Context().Value(keyIPInfo).(*geoloc.IPInfo)
|
||||
if !ok {
|
||||
renderResponse(w, r, errRender(errors.New("could not get ipinfo object")))
|
||||
return
|
||||
}
|
||||
|
||||
renderResponse(w, r, newIPInfoResponse(ipinfo))
|
||||
}
|
||||
|
||||
func (lh *LocationHandler) SearchIPHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
var ipinfo *geoloc.IPInfo
|
||||
|
||||
if ipAddress := chi.URLParam(r, "ipAddress"); ipAddress != "" {
|
||||
ipnetnum, err := geoloc.CalculateIPNum(ipAddress)
|
||||
if err != nil {
|
||||
renderResponse(w, r, errInvalidRequest(err))
|
||||
return
|
||||
}
|
||||
|
||||
newipnet, found := lh.cache.Get(ipnetnum)
|
||||
if found {
|
||||
ipnetnum = newipnet
|
||||
}
|
||||
|
||||
ipinfo, err = lh.db.SearchIPNet(ipnetnum)
|
||||
if err != nil {
|
||||
renderResponse(w, r, errNotFound())
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
lh.cache.Set(ipnetnum, ipinfo.IPNumFrom)
|
||||
}
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), keyIPInfo, ipinfo)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
39
api/v1/renderer.go
Normal file
39
api/v1/renderer.go
Normal file
|
@ -0,0 +1,39 @@
|
|||
package apiv1
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func errNotFound() render.Renderer {
|
||||
return &errResponse{
|
||||
HTTPStatusCode: http.StatusNotFound,
|
||||
StatusText: "Resource not found",
|
||||
}
|
||||
}
|
||||
|
||||
func errInvalidRequest(err error) render.Renderer {
|
||||
return &errResponse{
|
||||
Err: err,
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
StatusText: "Invalid request",
|
||||
ErrorText: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func errRender(err error) render.Renderer {
|
||||
return &errResponse{
|
||||
Err: err,
|
||||
HTTPStatusCode: http.StatusUnprocessableEntity,
|
||||
StatusText: "Error rendering response",
|
||||
ErrorText: err.Error(),
|
||||
}
|
||||
}
|
||||
|
||||
func renderResponse(w http.ResponseWriter, r *http.Request, v render.Renderer) {
|
||||
if err := render.Render(w, r, v); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -1,76 +1,28 @@
|
|||
package api_v1
|
||||
package apiv1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.ar21.de/yolokube/country-geo-locations/pkg/geoloc"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/render"
|
||||
)
|
||||
|
||||
func ApiRouter() chi.Router {
|
||||
func NewRouter(lh *LocationHandler) 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)
|
||||
r.Use(lh.SearchIPHandler)
|
||||
r.Get("/", lh.SearchIPHandlerFunc)
|
||||
})
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
newipnet, found := geoloc.GetCacheContent(ipnetnum)
|
||||
if found {
|
||||
ipnetnum = newipnet
|
||||
}
|
||||
|
||||
ipinfo, err = geoloc.SearchIPNet(ipnetnum)
|
||||
if err != nil {
|
||||
render.Render(w, r, errNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if !found {
|
||||
geoloc.SetCacheContent(ipnetnum, ipinfo.IPNumFrom, 2*time.Minute)
|
||||
}
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), keyIPInfo, ipinfo)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
type errResponse struct {
|
||||
Err error `json:"-"`
|
||||
HTTPStatusCode int `json:"-"`
|
||||
|
@ -80,31 +32,11 @@ type errResponse struct {
|
|||
ErrorText string `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (e *errResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
func (e *errResponse) Render(_ 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
|
||||
|
@ -113,7 +45,7 @@ type ipInfoResponse struct {
|
|||
*geoloc.IPInfo
|
||||
}
|
||||
|
||||
func (ir *ipInfoResponse) Render(w http.ResponseWriter, r *http.Request) error {
|
||||
func (ir *ipInfoResponse) Render(_ http.ResponseWriter, _ *http.Request) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue