package main import ( "context" "embed" "fmt" "io/fs" "log" "net/http" "os" "os/signal" "syscall" "time" "simplefinancedash/backend/internal/api" "simplefinancedash/backend/internal/db" ) // These directories are populated at build time: // - frontend_dist/ is copied from frontend/dist by the Dockerfile // - migrations/ is symlinked or copied from backend/migrations/ //go:embed frontend_dist var frontendFiles embed.FS //go:embed migrations var migrationsFiles embed.FS func main() { ctx, cancel := context.WithCancel(context.Background()) defer cancel() databaseURL := getEnv("DATABASE_URL", "postgres://simplefin:simplefin@localhost:5432/simplefindb?sslmode=disable") sessionSecret := getEnv("SESSION_SECRET", "change-me-in-production") port := getEnv("PORT", "8080") // Connect to database pool, err := db.Connect(ctx, databaseURL) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer pool.Close() // Run migrations migrationsFS, err := fs.Sub(migrationsFiles, "migrations") if err != nil { log.Fatalf("Failed to setup migrations filesystem: %v", err) } if err := db.RunMigrations(ctx, pool, migrationsFS); err != nil { log.Fatalf("Failed to run migrations: %v", err) } log.Println("Migrations completed") // Setup frontend filesystem frontendFS, err := fs.Sub(frontendFiles, "frontend_dist") if err != nil { log.Fatalf("Failed to setup frontend filesystem: %v", err) } // Create router queries := db.NewQueries(pool) router := api.NewRouter(queries, sessionSecret, frontendFS) // Start server server := &http.Server{ Addr: ":" + port, Handler: router, ReadTimeout: 15 * time.Second, WriteTimeout: 15 * time.Second, IdleTimeout: 60 * time.Second, } go func() { log.Printf("Server starting on :%s", port) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed: %v", err) } }() // Graceful shutdown quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") shutdownCtx, shutdownCancel := context.WithTimeout(ctx, 10*time.Second) defer shutdownCancel() if err := server.Shutdown(shutdownCtx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } fmt.Println("Server stopped") } func getEnv(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback }