(Feat): Initial Commit
This commit is contained in:
252
internal/api/router/router.go
Normal file
252
internal/api/router/router.go
Normal file
@@ -0,0 +1,252 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"allaboutapps.dev/aw/go-starter/internal/api"
|
||||
"allaboutapps.dev/aw/go-starter/internal/api/handlers"
|
||||
"allaboutapps.dev/aw/go-starter/internal/api/handlers/constants"
|
||||
"allaboutapps.dev/aw/go-starter/internal/api/middleware"
|
||||
"allaboutapps.dev/aw/go-starter/internal/api/router/templates"
|
||||
"github.com/labstack/echo-contrib/echoprometheus"
|
||||
"github.com/labstack/echo/v4"
|
||||
echoMiddleware "github.com/labstack/echo/v4/middleware"
|
||||
"github.com/rs/zerolog/log"
|
||||
|
||||
// #nosec G108 - pprof handlers (conditionally made available via http.DefaultServeMux)
|
||||
"net/http/pprof"
|
||||
)
|
||||
|
||||
func Init(s *api.Server) error {
|
||||
s.Echo = echo.New()
|
||||
|
||||
viewsRenderer := &echoRenderer{
|
||||
templates: map[templates.ViewTemplate]*template.Template{},
|
||||
}
|
||||
|
||||
files, err := os.ReadDir(s.Config.Echo.WebTemplatesViewsBaseDirAbs)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read views templates dir: %w", err)
|
||||
}
|
||||
|
||||
for _, file := range files {
|
||||
if file.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
templateName := file.Name()
|
||||
t, err := template.New(templateName).ParseGlob(filepath.Join(s.Config.Echo.WebTemplatesViewsBaseDirAbs, templateName))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse template file: %w", err)
|
||||
}
|
||||
|
||||
viewsRenderer.templates[templates.ViewTemplate(templateName)] = t
|
||||
}
|
||||
|
||||
s.Echo.Renderer = viewsRenderer
|
||||
|
||||
s.Echo.Debug = s.Config.Echo.Debug
|
||||
s.Echo.HideBanner = true
|
||||
s.Echo.Logger.SetOutput(&echoLogger{level: s.Config.Logger.RequestLevel, log: log.With().Str("component", "echo").Logger()})
|
||||
echo.NotFoundHandler = NotFoundHandler(s.Config)
|
||||
|
||||
s.Echo.HTTPErrorHandler = HTTPErrorHandlerWithConfig(HTTPErrorHandlerConfig{
|
||||
HideInternalServerErrorDetails: s.Config.Echo.HideInternalServerErrorDetails,
|
||||
})
|
||||
|
||||
// ---
|
||||
// General middleware
|
||||
if s.Config.Management.EnableMetrics {
|
||||
s.Echo.Use(echoprometheus.NewMiddleware(""))
|
||||
err := s.Metrics.RegisterMetrics(context.Background())
|
||||
if err != nil {
|
||||
log.Err(err).Msg("Failed to register metrics")
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
log.Warn().Msg("Disabling metrics middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableTrailingSlashMiddleware {
|
||||
s.Echo.Pre(echoMiddleware.RemoveTrailingSlash())
|
||||
} else {
|
||||
log.Warn().Msg("Disabling trailing slash middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableRecoverMiddleware {
|
||||
s.Echo.Use(echoMiddleware.RecoverWithConfig(echoMiddleware.RecoverConfig{
|
||||
LogErrorFunc: middleware.LogErrorFuncWithRequestInfo,
|
||||
}))
|
||||
} else {
|
||||
log.Warn().Msg("Disabling recover middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableSecureMiddleware {
|
||||
s.Echo.Use(echoMiddleware.SecureWithConfig(echoMiddleware.SecureConfig{
|
||||
Skipper: echoMiddleware.DefaultSecureConfig.Skipper,
|
||||
XSSProtection: s.Config.Echo.SecureMiddleware.XSSProtection,
|
||||
ContentTypeNosniff: s.Config.Echo.SecureMiddleware.ContentTypeNosniff,
|
||||
XFrameOptions: s.Config.Echo.SecureMiddleware.XFrameOptions,
|
||||
HSTSMaxAge: s.Config.Echo.SecureMiddleware.HSTSMaxAge,
|
||||
HSTSExcludeSubdomains: s.Config.Echo.SecureMiddleware.HSTSExcludeSubdomains,
|
||||
ContentSecurityPolicy: s.Config.Echo.SecureMiddleware.ContentSecurityPolicy,
|
||||
CSPReportOnly: s.Config.Echo.SecureMiddleware.CSPReportOnly,
|
||||
HSTSPreloadEnabled: s.Config.Echo.SecureMiddleware.HSTSPreloadEnabled,
|
||||
ReferrerPolicy: s.Config.Echo.SecureMiddleware.ReferrerPolicy,
|
||||
}))
|
||||
} else {
|
||||
log.Warn().Msg("Disabling secure middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableRequestIDMiddleware {
|
||||
s.Echo.Use(echoMiddleware.RequestID())
|
||||
} else {
|
||||
log.Warn().Msg("Disabling request ID middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableLoggerMiddleware {
|
||||
s.Echo.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{
|
||||
Level: s.Config.Logger.RequestLevel,
|
||||
LogRequestBody: s.Config.Logger.LogRequestBody,
|
||||
LogRequestHeader: s.Config.Logger.LogRequestHeader,
|
||||
LogRequestQuery: s.Config.Logger.LogRequestQuery,
|
||||
LogResponseBody: s.Config.Logger.LogResponseBody,
|
||||
LogResponseHeader: s.Config.Logger.LogResponseHeader,
|
||||
LogCaller: s.Config.Logger.LogCaller,
|
||||
RequestBodyLogSkipper: func(req *http.Request) bool {
|
||||
// We skip all body logging for auth endpoints as these might contain credentials
|
||||
if strings.HasPrefix(req.URL.Path, "/api/v1/auth") {
|
||||
return true
|
||||
}
|
||||
|
||||
return middleware.DefaultRequestBodyLogSkipper(req)
|
||||
},
|
||||
ResponseBodyLogSkipper: func(req *http.Request, res *echo.Response) bool {
|
||||
// We skip all body logging for auth endpoints as these might contain credentials
|
||||
if strings.HasPrefix(req.URL.Path, "/api/v1/auth") {
|
||||
return true
|
||||
}
|
||||
|
||||
return middleware.DefaultResponseBodyLogSkipper(req, res)
|
||||
},
|
||||
Skipper: func(c echo.Context) bool {
|
||||
// We skip logging of readiness and liveness endpoints
|
||||
switch c.Path() {
|
||||
case "/-/ready", "/-/healthy":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}))
|
||||
} else {
|
||||
log.Warn().Msg("Disabling logger middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableCORSMiddleware {
|
||||
s.Echo.Use(echoMiddleware.CORS())
|
||||
} else {
|
||||
log.Warn().Msg("Disabling CORS middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Echo.EnableCacheControlMiddleware {
|
||||
s.Echo.Use(middleware.CacheControl())
|
||||
} else {
|
||||
log.Warn().Msg("Disabling cache control middleware due to environment config")
|
||||
}
|
||||
|
||||
if s.Config.Pprof.Enable {
|
||||
pprofAuthMiddleware := middleware.Noop()
|
||||
|
||||
if s.Config.Pprof.EnableManagementKeyAuth {
|
||||
pprofAuthMiddleware = echoMiddleware.KeyAuthWithConfig(echoMiddleware.KeyAuthConfig{
|
||||
KeyLookup: "query:mgmt-secret",
|
||||
Validator: func(key string, _ echo.Context) (bool, error) {
|
||||
return key == s.Config.Management.Secret, nil
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
s.Echo.GET("/debug/pprof", echo.WrapHandler(http.HandlerFunc(pprof.Index)), pprofAuthMiddleware)
|
||||
s.Echo.Any("/debug/pprof/*", echo.WrapHandler(http.DefaultServeMux), pprofAuthMiddleware)
|
||||
|
||||
log.Warn().Bool("EnableManagementKeyAuth", s.Config.Pprof.EnableManagementKeyAuth).Msg("Pprof http handlers are available at /debug/pprof")
|
||||
|
||||
if s.Config.Pprof.RuntimeBlockProfileRate != 0 {
|
||||
runtime.SetBlockProfileRate(s.Config.Pprof.RuntimeBlockProfileRate)
|
||||
log.Warn().Int("RuntimeBlockProfileRate", s.Config.Pprof.RuntimeBlockProfileRate).Msg("Pprof runtime.SetBlockProfileRate")
|
||||
}
|
||||
|
||||
if s.Config.Pprof.RuntimeMutexProfileFraction != 0 {
|
||||
runtime.SetMutexProfileFraction(s.Config.Pprof.RuntimeMutexProfileFraction)
|
||||
log.Warn().Int("RuntimeMutexProfileFraction", s.Config.Pprof.RuntimeMutexProfileFraction).Msg("Pprof runtime.SetMutexProfileFraction")
|
||||
}
|
||||
}
|
||||
|
||||
// Add your custom / additional middlewares here.
|
||||
// see https://echo.labstack.com/middleware
|
||||
|
||||
// ---
|
||||
// Initialize our general groups and set middleware to use above them
|
||||
s.Router = &api.Router{
|
||||
Routes: nil, // will be populated by handlers.AttachAllRoutes(s)
|
||||
|
||||
// Unsecured base group available at /**
|
||||
Root: s.Echo.Group(""),
|
||||
|
||||
// Management endpoints, uncacheable, secured by key auth (query param), available at /-/**
|
||||
Management: s.Echo.Group("/-", echoMiddleware.KeyAuthWithConfig(echoMiddleware.KeyAuthConfig{
|
||||
KeyLookup: "query:mgmt-secret",
|
||||
Validator: func(key string, _ echo.Context) (bool, error) {
|
||||
return key == s.Config.Management.Secret, nil
|
||||
},
|
||||
Skipper: func(c echo.Context) bool {
|
||||
//nolint:gocritic
|
||||
switch c.Path() {
|
||||
case "/-/ready":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
}), middleware.NoCache()),
|
||||
|
||||
// OAuth2, unsecured or secured by bearer auth, available at /api/v1/auth/**
|
||||
APIV1Auth: s.Echo.Group("/api/v1/auth", middleware.AuthWithConfig(middleware.AuthConfig{
|
||||
S: s,
|
||||
Mode: middleware.AuthModeRequired,
|
||||
Skipper: func(c echo.Context) bool {
|
||||
switch c.Path() {
|
||||
case "/api/v1/auth/forgot-password",
|
||||
"/api/v1/auth/forgot-password/complete",
|
||||
"/api/v1/auth/login",
|
||||
"/api/v1/auth/refresh",
|
||||
"/api/v1/auth/register",
|
||||
fmt.Sprintf("/api/v1/auth/register/:%s", constants.RegistrationTokenParam):
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
})),
|
||||
WellKnown: s.Echo.Group("/.well-known"),
|
||||
|
||||
// Your other endpoints, typically secured by bearer auth, available at /api/v1/**
|
||||
APIV1Push: s.Echo.Group("/api/v1/push", middleware.Auth(s)),
|
||||
}
|
||||
|
||||
// ---
|
||||
// Finally attach our handlers
|
||||
handlers.AttachAllRoutes(s)
|
||||
|
||||
if s.Config.Management.EnableMetrics {
|
||||
log.Info().Msg("Metrics enabled and available under /metrics")
|
||||
s.Echo.GET("/metrics", echoprometheus.NewHandler())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user