package server import ( "context" "fmt" "net/http" "os" "github.com/sirupsen/logrus" "markdown-editor/internal/storage" "markdown-editor/internal/api" "markdown-editor/internal/logger" "github.com/gin-gonic/gin" ) type Server struct { dataDir string port int host string storage *storage.Storage api *api.API router *gin.Engine httpSrv *http.Server log *logrus.Logger } func NewServer(dataDir string, port int, host string) *Server { log := logger.GetLogger() s := &Server{ dataDir: dataDir, port: port, host: host, log: log, } var err error s.storage, err = storage.NewStorage(dataDir) if err != nil { log.Fatalf("Failed to initialize storage: %v", err) } s.api = api.NewAPI(s.storage) // Initialize Gin router s.router = gin.New() s.router.Use(gin.Logger()) s.router.Use(gin.Recovery()) return s } func (s *Server) Start() error { s.log.Info("Starting HTTP server") // Register API routes s.api.RegisterRoutes(s.router) // Serve static files from frontend build s.serveStaticAssets() // Build the URL url := fmt.Sprintf("%s:%d", s.host, s.port) s.log.Infof("Server listening on %s", url) s.httpSrv = &http.Server{ Addr: url, Handler: s.router, } return s.httpSrv.ListenAndServe() } func (s *Server) serveStaticAssets() { // Try to serve from multiple possible locations possiblePaths := []string{ "./frontend/dist", "frontend/dist", } var assetPath string for _, path := range possiblePaths { if _, err := os.Stat(path); err == nil { assetPath = path break } } if assetPath == "" { s.log.Warn("Frontend build not found, serving API only") return } s.log.Infof("Serving static assets from: %s", assetPath) // Serve files from the dist directory - use /dist prefix to avoid conflicts with /api s.router.Static("/dist", assetPath) } func (s *Server) Shutdown(ctx context.Context) error { s.log.Info("Shutting down HTTP server") // Clean up storage if s.storage != nil { s.storage = nil } if s.httpSrv != nil { return s.httpSrv.Shutdown(ctx) } return nil }