package server import ( "context" "embed" "errors" "fmt" "net/http" "os" "os/signal" "time" "github.com/getsentry/sentry-go" sentryecho "github.com/getsentry/sentry-go/echo" "github.com/gorilla/sessions" "github.com/labstack/echo-contrib/session" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/rjNemo/rentease/internal/config" "github.com/rjNemo/rentease/internal/service/auth" "github.com/rjNemo/rentease/internal/service/booking" ) type Server struct { Router *echo.Echo bs *booking.Service as *auth.Service hc *config.Host addr string stripeWebhookSecret string } func New(bs *booking.Service, as *auth.Service, hc *config.Host, opts ...Option) (*Server, error) { option := new(options) for _, opt := range opts { err := opt(option) if err != nil { return nil, err } } s := &Server{ Router: NewRouter(*option.fs, *option.debug, *option.secretKey, option.origins), bs: bs, as: as, hc: hc, addr: fmt.Sprintf("0.0.0.0:%d", *option.port), stripeWebhookSecret: "", } if option.stripeWebhookSecret != nil { s.stripeWebhookSecret = *option.stripeWebhookSecret } s.MountHandlers() return s, nil } func (s Server) Start(c context.Context) { go func() { if err := s.Router.Start(s.addr); err != nil && !errors.Is(err, http.ErrServerClosed) { s.Router.Logger.Fatalf("shutting down the server: %s", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, os.Interrupt) <-quit ctx, cancel := context.WithTimeout(c, 10*time.Second) defer cancel() if err := s.Router.Shutdown(ctx); err != nil { s.Router.Logger.Fatal(err) } } func NewRouter(fs embed.FS, debug bool, secret string, origins []string) *echo.Echo { e := echo.New() // config e.HideBanner = !debug e.Debug = debug e.HTTPErrorHandler = func(err error, c echo.Context) { captureError(c, err) code := http.StatusInternalServerError var he *echo.HTTPError if errors.As(err, &he) { code = he.Code } errorPage := fmt.Sprintf("assets/html/HTTP%d.html", code) if err := c.File(errorPage); err != nil { c.Logger().Error(err) } } // middlewares e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "${time_rfc3339} [${method}: ${status}] ${uri}; ip=${remote_ip}; ${latency_human}; ${user_agent}\n", })) e.Use(middleware.Recover()) e.Use(middleware.Secure()) e.Use(middleware.Gzip()) e.Use(middleware.CORSWithConfig(middleware.CORSConfig{AllowOrigins: origins})) e.Use(sentryecho.New(sentryecho.Options{})) e.Use(SentryTracingMiddleware) e.Use(CachingMiddleware(0, "js", "css", "png", "ico")) e.Use(session.Middleware(sessions.NewCookieStore([]byte(secret)))) // static assets e.StaticFS("/static", echo.MustSubFS(fs, "assets")) return e } func captureError(c echo.Context, err error) { if hub := sentryecho.GetHubFromContext(c); hub != nil { hub.WithScope(func(s *sentry.Scope) { hub.CaptureMessage(err.Error()) }) } }