sessions/internal/router/rest/rest.go

138 lines
3.3 KiB
Go

package rest
import (
"encoding/json"
"errors"
"net/http"
"gitea.dwysokinski.me/twhelp/sessions/internal/domain"
"gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/docs"
"gitea.dwysokinski.me/twhelp/sessions/internal/router/rest/internal/model"
"github.com/go-chi/chi/v5"
"github.com/go-chi/cors"
httpSwagger "github.com/swaggo/http-swagger"
)
//go:generate counterfeiter -generate
func New(
apiKeyVerifier APIKeyVerifier,
sessionSvc SessionService,
opts ...Option,
) *chi.Mux {
cfg := newConfig(opts...)
// handlers
uh := userHandler{}
sh := sessionHandler{
svc: sessionSvc,
}
router := chi.NewRouter()
if cfg.cors.Enabled {
router.Use(cors.Handler(cors.Options{
AllowedOrigins: cfg.cors.AllowedOrigins,
AllowCredentials: cfg.cors.AllowCredentials,
AllowedMethods: cfg.cors.AllowedMethods,
AllowedHeaders: []string{"Origin", "Content-Length", "Content-Type", "X-API-Key"},
MaxAge: cfg.cors.MaxAge,
}))
}
router.NotFound(routeNotFoundHandler)
router.MethodNotAllowed(methodNotAllowedHandler)
router.Route("/v1", func(r chi.Router) {
if cfg.swagger.Enabled {
if cfg.swagger.Host != "" {
docs.SwaggerInfo.Host = cfg.swagger.Host
}
if len(cfg.swagger.Schemes) > 0 {
docs.SwaggerInfo.Schemes = cfg.swagger.Schemes
}
r.Get("/swagger/*", httpSwagger.Handler())
}
authMw := authMiddleware(apiKeyVerifier)
r.With(authMw).Get("/user", uh.getCurrent)
r.With(authMw).Put("/user/sessions/{serverKey}", sh.createOrUpdate)
r.With(authMw).Get("/user/sessions/{serverKey}", sh.getCurrentUser)
})
return router
}
func routeNotFoundHandler(w http.ResponseWriter, _ *http.Request) {
renderJSON(w, http.StatusNotFound, model.ErrorResp{
Error: model.APIError{
Code: "route-not-found",
Message: "route not found",
},
})
}
func methodNotAllowedHandler(w http.ResponseWriter, _ *http.Request) {
renderJSON(w, http.StatusMethodNotAllowed, model.ErrorResp{
Error: model.APIError{
Code: "method-not-allowed",
Message: "method not allowed",
},
})
}
type internalServerError struct {
err error
}
func (e internalServerError) Error() string {
return e.err.Error()
}
func (e internalServerError) UserError() string {
return "internal server error"
}
func (e internalServerError) Code() domain.ErrorCode {
return domain.ErrorCodeUnknown
}
func (e internalServerError) Unwrap() error {
return e.err
}
func renderErr(w http.ResponseWriter, err error) {
var domainErr domain.Error
if !errors.As(err, &domainErr) {
domainErr = internalServerError{err: err}
}
renderJSON(w, errorCodeToHTTPStatus(domainErr.Code()), model.ErrorResp{
Error: model.APIError{
Code: domainErr.Code().String(),
Message: domainErr.UserError(),
},
})
}
func errorCodeToHTTPStatus(code domain.ErrorCode) int {
switch code {
case domain.ErrorCodeValidationError:
return http.StatusBadRequest
case domain.ErrorCodeEntityNotFound:
return http.StatusNotFound
case domain.ErrorCodeAlreadyExists:
return http.StatusConflict
case domain.ErrorCodeUnknown:
fallthrough
default:
return http.StatusInternalServerError
}
}
func renderJSON(w http.ResponseWriter, status int, data any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
_ = json.NewEncoder(w).Encode(data)
}