mirror of
https://github.com/rjNemo/rentease.git
synced 2026-06-06 02:36:49 +00:00
134 lines
3.2 KiB
Go
134 lines
3.2 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"errors"
|
|
"fmt"
|
|
"io/fs"
|
|
"log/slog"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"time"
|
|
|
|
sentryhttp "github.com/getsentry/sentry-go/http"
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
"github.com/go-chi/cors"
|
|
|
|
"github.com/rjNemo/rentease/internal/config"
|
|
"github.com/rjNemo/rentease/internal/service/auth"
|
|
"github.com/rjNemo/rentease/internal/service/booking"
|
|
)
|
|
|
|
type Server struct {
|
|
Router *chi.Mux
|
|
httpServer *http.Server
|
|
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 {
|
|
if err := opt(option); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
router, err := NewRouter(*option.fs, *option.debug, option.origins)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := &Server{
|
|
Router: router,
|
|
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(ctx context.Context) {
|
|
s.httpServer = &http.Server{
|
|
Addr: s.addr,
|
|
Handler: s.Router,
|
|
}
|
|
|
|
go func() {
|
|
if err := s.httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
slog.Error("shutting down the server", slog.Any("error", err))
|
|
os.Exit(1)
|
|
}
|
|
}()
|
|
|
|
quit := make(chan os.Signal, 1)
|
|
signal.Notify(quit, os.Interrupt)
|
|
defer signal.Stop(quit)
|
|
|
|
select {
|
|
case <-quit:
|
|
case <-ctx.Done():
|
|
}
|
|
|
|
shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
if err := s.httpServer.Shutdown(shutdownCtx); err != nil {
|
|
slog.Error("server shutdown failed", slog.Any("error", err))
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func NewRouter(filesystem embed.FS, debug bool, origins []string) (*chi.Mux, error) {
|
|
r := chi.NewRouter()
|
|
_ = debug
|
|
|
|
r.Use(middleware.RequestID)
|
|
r.Use(middleware.RealIP)
|
|
r.Use(middleware.Logger)
|
|
r.Use(middleware.Recoverer)
|
|
r.Use(middleware.Compress(5))
|
|
r.Use(CachingMiddleware(0, "js", "css", "png", "ico"))
|
|
|
|
if len(origins) == 0 {
|
|
origins = []string{"*"}
|
|
}
|
|
|
|
r.Use(cors.Handler(cors.Options{
|
|
AllowedOrigins: origins,
|
|
AllowedMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete, http.MethodOptions},
|
|
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token", "HX-Request", "HX-Boosted", "api-key"},
|
|
ExposedHeaders: []string{"HX-Redirect"},
|
|
AllowCredentials: true,
|
|
}))
|
|
|
|
sentryHandler := sentryhttp.New(sentryhttp.Options{
|
|
Repanic: true,
|
|
})
|
|
r.Use(sentryHandler.Handle)
|
|
r.Use(SentryTracingMiddleware)
|
|
|
|
assetsFS, err := fs.Sub(filesystem, "assets")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load static assets: %w", err)
|
|
}
|
|
|
|
fileServer := http.StripPrefix("/static/", http.FileServer(http.FS(assetsFS)))
|
|
r.Handle("/static/*", fileServer)
|
|
|
|
return r, nil
|
|
}
|