148 lines
5.0 KiB
Go
148 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"log/slog"
|
|
"net/http"
|
|
_ "net/http/pprof"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/labstack/echo/v4/middleware"
|
|
"github.com/lowcarbdev/sbv/internal"
|
|
)
|
|
|
|
var logger *slog.Logger
|
|
|
|
func main() {
|
|
// Initialize slog logger
|
|
logger = slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
|
|
Level: slog.LevelInfo,
|
|
}))
|
|
slog.SetDefault(logger)
|
|
|
|
// Initialize authentication database
|
|
dbPathPrefix := os.Getenv("DB_PATH_PREFIX")
|
|
if dbPathPrefix == "" {
|
|
dbPathPrefix = "."
|
|
}
|
|
authDBPath := dbPathPrefix + "/sbv.db"
|
|
|
|
err := internal.InitAuthDB(authDBPath)
|
|
if err != nil {
|
|
logger.Error("Failed to initialize authentication database", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("Authentication database initialized", "path", authDBPath)
|
|
|
|
// Create Echo instance
|
|
e := echo.New()
|
|
|
|
// Middleware
|
|
e.Use(middleware.Logger())
|
|
e.Use(middleware.Recover())
|
|
|
|
// Use custom CORS middleware that properly handles credentials
|
|
e.Use(internal.CustomCORSMiddleware())
|
|
|
|
// Configure timeouts for large file uploads
|
|
e.Server.ReadTimeout = 30 * time.Minute
|
|
e.Server.WriteTimeout = 30 * time.Minute
|
|
e.Server.ReadHeaderTimeout = 1 * time.Minute
|
|
e.Server.IdleTimeout = 2 * time.Minute
|
|
e.Server.MaxHeaderBytes = 1 << 20 // 1 MB max header size
|
|
|
|
// Public routes (no authentication required)
|
|
// Apply NoCacheMiddleware to prevent browser caching of auth responses
|
|
e.POST("/api/auth/register", internal.HandleRegister, internal.NoCacheMiddleware)
|
|
e.POST("/api/auth/login", internal.HandleLogin, internal.NoCacheMiddleware)
|
|
e.POST("/api/auth/logout", internal.HandleLogout, internal.NoCacheMiddleware)
|
|
|
|
// Protected routes (authentication required)
|
|
protected := e.Group("/api")
|
|
protected.Use(internal.AuthMiddleware)
|
|
protected.Use(internal.NoCacheMiddleware) // Prevent browser caching of API responses
|
|
|
|
protected.GET("/auth/me", internal.HandleMe)
|
|
protected.POST("/auth/change-password", internal.HandleChangePassword)
|
|
protected.POST("/upload", internal.HandleUpload)
|
|
protected.GET("/conversations", internal.HandleConversations)
|
|
protected.GET("/messages", internal.HandleMessages)
|
|
protected.GET("/activity", internal.HandleActivity)
|
|
protected.GET("/calls", internal.HandleCalls)
|
|
protected.GET("/daterange", internal.HandleDateRange)
|
|
protected.GET("/progress", internal.HandleProgress)
|
|
protected.GET("/media", internal.HandleMedia)
|
|
protected.GET("/media-items", internal.HandleMediaItems)
|
|
protected.GET("/search", internal.HandleSearch)
|
|
protected.GET("/settings", internal.HandleGetSettings)
|
|
protected.PUT("/settings", internal.HandleUpdateSettings)
|
|
|
|
// Health check
|
|
e.GET("/api/health", func(c echo.Context) error {
|
|
return c.String(http.StatusOK, "OK")
|
|
})
|
|
|
|
// Version endpoint (public, no authentication required)
|
|
e.GET("/api/version", internal.HandleVersion)
|
|
|
|
// Serve static files from frontend/dist if it exists (for production/Docker)
|
|
if _, err := os.Stat("./frontend/dist"); err == nil {
|
|
// Serve static assets (JS, CSS, images, etc.)
|
|
e.Static("/assets", "./frontend/dist/assets")
|
|
e.File("/favicon.ico", "./frontend/dist/favicon.ico")
|
|
e.File("/favicon.svg", "./frontend/dist/favicon.svg")
|
|
e.File("/apple-touch-icon.png", "./frontend/dist/apple-touch-icon.png")
|
|
e.File("/favicon-96x96.png", "./frontend/dist/favicon-96x96.png")
|
|
e.File("/web-app-manifest-192x192.png", "./frontend/dist/web-app-manifest-192x192.png")
|
|
e.File("/web-app-manifest-512x512.png", "./frontend/dist/web-app-manifest-512x512.png")
|
|
e.File("/site.webmanifest", "./frontend/dist/site.webmanifest")
|
|
|
|
// SPA fallback - serve index.html for all non-API routes
|
|
// This must be last so it doesn't interfere with API routes
|
|
e.GET("/*", func(c echo.Context) error {
|
|
return c.File("./frontend/dist/index.html")
|
|
})
|
|
|
|
logger.Info("Serving static files from ./frontend/dist with SPA routing support")
|
|
}
|
|
|
|
// Start pprof server in a separate goroutine for profiling
|
|
go func() {
|
|
port := os.Getenv("PORT")
|
|
if port == "" {
|
|
port = "8081"
|
|
}
|
|
pprofPort := "6060"
|
|
logger.Info("Memory profiling available", "url", "http://localhost:"+pprofPort+"/debug/pprof/")
|
|
if err := http.ListenAndServe(":"+pprofPort, nil); err != nil {
|
|
logger.Error("pprof server failed", "error", err)
|
|
}
|
|
}()
|
|
|
|
port := os.Getenv("PORT")
|
|
if port == "" {
|
|
port = "8081"
|
|
}
|
|
|
|
// Create HTTP server with longer timeouts for large file uploads
|
|
server := &http.Server{
|
|
Addr: ":" + port,
|
|
ReadTimeout: 30 * time.Minute, // Allow 30 minutes for reading large uploads
|
|
WriteTimeout: 30 * time.Minute, // Allow 30 minutes for writing responses
|
|
ReadHeaderTimeout: 1 * time.Minute, // Header read timeout
|
|
IdleTimeout: 2 * time.Minute, // Idle connection timeout
|
|
MaxHeaderBytes: 1 << 20, // 1 MB max header size
|
|
}
|
|
|
|
logger.Info("Server starting", "port", port)
|
|
logger.Info("Upload timeout set to 30 minutes for large backup files")
|
|
|
|
e.Server = server
|
|
// Start server
|
|
if err := e.Start(":" + port); err != nil && err != http.ErrServerClosed {
|
|
logger.Error("Server failed to start", "error", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|