package main import ( "context" "errors" "net/http" "os/signal" "syscall" "time" "github.com/tdvorak/seen/backend/internal/api" "github.com/tdvorak/seen/backend/internal/api/handlers" "github.com/tdvorak/seen/backend/internal/config" "github.com/tdvorak/seen/backend/internal/downloader" "github.com/tdvorak/seen/backend/internal/integrations/cache" "github.com/tdvorak/seen/backend/internal/integrations/igdb" "github.com/tdvorak/seen/backend/internal/repositories/postgres" "github.com/tdvorak/seen/backend/internal/scanner" "github.com/tdvorak/seen/backend/internal/services/auth" "github.com/tdvorak/seen/backend/internal/services/catalog" "github.com/tdvorak/seen/backend/internal/services/download" "github.com/tdvorak/seen/backend/internal/workers" "github.com/tdvorak/seen/backend/pkg/logger" "go.uber.org/zap" ) func main() { cfg := config.Load() log := logger.New(cfg.Env) defer func() { _ = log.Sync() }() ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) defer stop() pgPool, err := postgres.NewPool(ctx, cfg.Postgres, log) if err != nil { log.Fatal("postgres startup failed", zap.Error(err)) } defer pgPool.Close() cacheManager, err := cache.NewManager(ctx, cfg.Cache, log) if err != nil { log.Fatal("dragonfly startup failed", zap.Error(err)) } defer func() { if err := cacheManager.Close(); err != nil { log.Error("failed to close cache manager", zap.Error(err)) } }() // Warmup cache with common data if err := cacheManager.WarmupCache(ctx); err != nil { log.Warn("cache warmup failed", zap.Error(err)) } authRepository := postgres.NewAuthRepository(pgPool) catalogRepository := postgres.NewCatalogRepository(pgPool) downloadRepository := postgres.NewDownloadRepository(pgPool) authService := auth.NewService(authRepository, cfg.Auth, log) catalogService := catalog.NewService(catalogRepository) downloadService := download.NewService(downloadRepository) igdbClient := igdb.NewClient(cfg.IGDB) if igdbClient.Enabled() { catalogService.SetGameLookup(igdbClient) log.Info("igdb live search enabled") } healthHandler := handlers.NewHealthHandler(pgPool, cacheManager.Client()) authHandler := handlers.NewAuthHandler(authService) catalogHandler := handlers.NewCatalogHandler(catalogService, authService) downloadHandler := handlers.NewDownloadHandler(downloadService, authService) placeholderHandler := handlers.NewPlaceholderHandler() router := api.NewRouter(cfg, log, api.Handlers{ Health: healthHandler, Auth: authHandler, Catalog: catalogHandler, Download: downloadHandler, Placeholder: placeholderHandler, }) workerManager := workers.NewManager( log, downloader.NewWorker(log), scanner.NewWorker(log), ) workerManager.Start(ctx) httpServer := &http.Server{ Addr: cfg.HTTP.Addr(), Handler: router, ReadTimeout: cfg.HTTP.ReadTimeout, WriteTimeout: cfg.HTTP.WriteTimeout, } go func() { log.Info("http server started", zap.String("addr", cfg.HTTP.Addr())) if err := httpServer.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { log.Fatal("http server failed", zap.Error(err)) } }() <-ctx.Done() log.Info("shutdown signal received") shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if err := httpServer.Shutdown(shutdownCtx); err != nil { log.Error("graceful shutdown failed", zap.Error(err)) } }