package main import ( "context" "net/http" "os" "os/signal" "syscall" "time" "git.pengzhan.dev/haopengzhan/rsspipe/internal/actions/readability" "git.pengzhan.dev/haopengzhan/rsspipe/internal/api" "git.pengzhan.dev/haopengzhan/rsspipe/internal/auth" "git.pengzhan.dev/haopengzhan/rsspipe/internal/config" "git.pengzhan.dev/haopengzhan/rsspipe/internal/crawler" "git.pengzhan.dev/haopengzhan/rsspipe/internal/db" "git.pengzhan.dev/haopengzhan/rsspipe/internal/events" "git.pengzhan.dev/haopengzhan/rsspipe/internal/logger" "git.pengzhan.dev/haopengzhan/rsspipe/internal/rules" "git.pengzhan.dev/haopengzhan/rsspipe/internal/worker" "go.uber.org/zap" ) func main() { log := logger.InitLogger(os.Getenv("LOG_LEVEL")) log.Info("Starting RSSPipe Server") cfg := config.LoadConfig() // 1. Initialize SQLite Database database, err := db.InitDB(cfg.DBPath) if err != nil { log.Fatal("Database initialization failed", zap.Error(err)) } defer func() { _ = database.Close() }() log.Info("Database connected", zap.String("path", cfg.DBPath)) // 2. Initialize Auth Service & Bootstrap Admin authService := auth.NewAuthService(database, cfg) if err := authService.BootstrapAdmin(); err != nil { log.Fatal("Admin bootstrap failed", zap.Error(err)) } ctx, cancel := context.WithCancel(context.Background()) defer cancel() // 3. Initialize EventBus, WorkerPool, and Action Handlers bus := events.NewEventBus() pool := worker.NewPool(database, bus, 5, 100) // Register Scraper Action scraper := readability.NewScraper(15 * time.Second) pool.RegisterHandler("readability_scrape", scraper.Handler()) pool.Start(ctx) // 4. Initialize Rules Engine engine := rules.NewEngine(database, bus, pool) engine.Start(ctx) // 5. Start Background Crawler Pool cr := crawler.NewCrawler(database, bus, cfg.CrawlerIntervalMinutes, cfg.CrawlerWorkers) cr.Start(ctx) // 6. Mount API Routes handler := api.NewHandler(database, authService, cr, pool) router := api.NewRouter(handler, authService, "./web/dist") server := &http.Server{ Addr: ":" + cfg.Port, Handler: router, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 120 * time.Second, } // 7. Graceful shutdown handler go func() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) <-sigChan log.Info("Shutting down RSSPipe gracefully...") cancel() pool.Stop() shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second) defer shutdownCancel() if err := server.Shutdown(shutdownCtx); err != nil { log.Error("Error during server shutdown", zap.Error(err)) } }() log.Info("RSSPipe HTTP Server listening", zap.String("url", "http://localhost:"+cfg.Port)) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal("Server error", zap.Error(err)) } log.Info("RSSPipe Server stopped") }