server/api/v0/server.go

127 lines
3.3 KiB
Go

/**
* file: api/v0/server.go
* author: Theo Technicguy
* license: Apache 2.0
*
* APIv0 Server entrypoint
*/
package apiv0
import (
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
hlogger "git.licolas.net/hoffman/server/logger"
"git.licolas.net/hoffman/server/types"
"github.com/gorilla/mux"
"github.com/justinas/alice"
"github.com/rs/zerolog"
"github.com/rs/zerolog/hlog"
)
const (
idQry = "id"
idVar = "{" + idQry + ":[0-9]+}"
)
var (
ErrNoSuchEndpoint = errors.New("no such endpoint")
ErrIdNotInt = errors.New("id is not an integer")
lgc types.LogicProvider
logger zerolog.Logger = hlogger.GetLogger("apiv0")
)
type apiError struct {
Code int `json:"code"`
Message string `json:"message"`
}
// respond writes the http ResponseWriter to answer the client with
// `status` status code and `content` JSON-encoded content.
func respond(w http.ResponseWriter, statusCode int, content any) {
logger.Debug().Int("code", statusCode).Any("content", content).Msg("responding client")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(content)
}
// respondWithError logs the error and replies with error & status code
func respondWithError(w http.ResponseWriter, statusCode int, err error) {
logger.Error().Err(err).Int("code", statusCode).Msg("error while processing request")
respond(w, statusCode, apiError{Code: statusCode, Message: err.Error()})
}
func errorNotFound(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json; charset=utf-8")
err := apiError{http.StatusNotFound, ErrNoSuchEndpoint.Error()}
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(err)
}
func errorMethodNotAllowed(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusMethodNotAllowed)
json.NewEncoder(w).Encode(apiError{
http.StatusMethodNotAllowed,
fmt.Sprintf("method %s not allowed for %s", r.Method, r.URL.Path),
})
}
// Middleware for setting headers
// Global to all v0 API endpoints
func headerMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json; charset=utf-8")
h.ServeHTTP(w, r)
})
}
func loggingMiddleware(next http.Handler) http.Handler {
chain := alice.New()
chain = chain.Append(hlog.NewHandler(logger))
chain.Append(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) {
hlog.FromRequest(r).Info().
Str("method", r.Method).
Stringer("url", r.URL).
Int("status", status).
Int("size", size).
Dur("duration", duration).
Msg("")
}))
chain = chain.Append(
hlog.RemoteAddrHandler("ip"),
hlog.UserAgentHandler("ua"),
hlog.RefererHandler("referrer"),
hlog.URLHandler("url"),
hlog.MethodHandler("method"),
func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
hlog.FromRequest(r).Info().Send()
next.ServeHTTP(w, r)
})
},
)
return chain.Then(next)
}
func SetLogicProvider(tlgc types.LogicProvider) {
lgc = tlgc
}
func SetRoutes(router *mux.Router) {
router.Use(headerMiddleware)
router.Use(loggingMiddleware)
SetProductTagRoutes(router)
SetProductRoutes(router)
router.NotFoundHandler = http.HandlerFunc(errorNotFound)
router.MethodNotAllowedHandler = http.HandlerFunc(errorMethodNotAllowed)
}