127 lines
3.3 KiB
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)
|
|
}
|