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