package main import ( "context" "log/slog" "net" "net/http" "time" "tangled.org/bnewbold.net/scrumble/indexer" "tangled.org/bnewbold.net/scrumble/store" "github.com/bluesky-social/indigo/atproto/identity" "github.com/bluesky-social/indigo/atproto/syntax" "github.com/bluesky-social/indigo/util/svcutil" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/prometheus/client_golang/prometheus/promhttp" "gorm.io/gorm" ) type Server struct { logger *slog.Logger dir identity.Directory store *store.Store indexer *indexer.Indexer } func NewServer(db *gorm.DB) (*Server, error) { st, err := store.NewStore(db) if err != nil { return nil, err } idx, err := indexer.NewIndexer(st, []syntax.DID{}) if err != nil { return nil, err } return &Server{ logger: slog.Default(), dir: identity.DefaultDirectory(), store: st, indexer: idx, }, nil } func (srv *Server) StartMetrics(listen string) error { http.Handle("/metrics", promhttp.Handler()) return http.ListenAndServe(listen, nil) } func (srv *Server) StartHTTP(bind string) error { var lc net.ListenConfig ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() li, err := lc.Listen(ctx, "tcp", bind) if err != nil { return err } return srv.startWithListener(li) } func (srv *Server) startWithListener(listen net.Listener) error { e := echo.New() e.HideBanner = true e.HidePort = true e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: []string{"*"}, AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization}, })) e.Use(func(next echo.HandlerFunc) echo.HandlerFunc { return func(c echo.Context) error { c.Response().Header().Set(echo.HeaderServer, "ScrumbleServer") return next(c) } }) e.Use(middleware.LoggerWithConfig(middleware.DefaultLoggerConfig)) e.File("/robots.txt", "static/robots.txt") e.Static("/static", "static") e.Use(svcutil.MetricsMiddleware) e.GET("/", srv.HandleHomeMessage) e.GET("/_health", srv.HandleHealthCheck) // In order to support booting on random ports in tests, we need to tell // the Echo instance it's already got a port, and then use its StartServer // method to re-use that listener. e.Listener = listen httpServer := &http.Server{} // TODO: attach echo to Server, for shutdown? return e.StartServer(httpServer) } func (srv *Server) StartIndexer() error { // TODO: wire through config return srv.indexer.Start("ws://localhost:2480/channel") } func (srv *Server) Shutdown() []error { var errs []error if err := srv.indexer.Shutdown(); err != nil { errs = append(errs, err) } // TODO: stop echo return errs }