package main import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time" "github.com/rs/zerolog/log" "gorm.io/gorm" "github.com/treytartt/casera-api/internal/config" "github.com/treytartt/casera-api/internal/database" "github.com/treytartt/casera-api/internal/push" "github.com/treytartt/casera-api/internal/router" "github.com/treytartt/casera-api/internal/services" "github.com/treytartt/casera-api/pkg/utils" ) func main() { // Load configuration cfg, err := config.Load() if err != nil { fmt.Printf("Failed to load configuration: %v\n", err) os.Exit(1) } // Initialize logger utils.InitLogger(cfg.Server.Debug) log.Info(). Bool("debug", cfg.Server.Debug). Int("port", cfg.Server.Port). Str("db_host", cfg.Database.Host). Int("db_port", cfg.Database.Port). Str("db_name", cfg.Database.Database). Str("db_user", cfg.Database.User). Str("redis_url", cfg.Redis.URL). Msg("Starting MyCrib API server") // Connect to database (retry with backoff) var db *gorm.DB var dbErr error for i := 0; i < 3; i++ { db, dbErr = database.Connect(&cfg.Database, cfg.Server.Debug) if dbErr == nil { break } log.Warn().Err(dbErr).Int("attempt", i+1).Msg("Failed to connect to database, retrying...") time.Sleep(time.Duration(i+1) * time.Second) } if dbErr != nil { log.Error().Err(dbErr).Msg("Failed to connect to database - API will start but database operations will fail") } else { defer database.Close() // Run database migrations only if connected if err := database.Migrate(); err != nil { log.Error().Err(err).Msg("Failed to run database migrations") } } // Connect to Redis (optional - don't fail if unavailable) var cache *services.CacheService cache, err = services.NewCacheService(&cfg.Redis) if err != nil { log.Warn().Err(err).Msg("Failed to connect to Redis - caching disabled") cache = nil } else { defer cache.Close() } // Initialize email service var emailService *services.EmailService log.Info(). Str("email_host", cfg.Email.Host). Str("email_user", cfg.Email.User). Str("email_from", cfg.Email.From). Int("email_port", cfg.Email.Port). Msg("Email config loaded") if cfg.Email.Host != "" && cfg.Email.User != "" { emailService = services.NewEmailService(&cfg.Email) log.Info(). Str("host", cfg.Email.Host). Msg("Email service initialized") } else { log.Warn(). Str("host", cfg.Email.Host). Str("user", cfg.Email.User). Msg("Email service not configured - emails will not be sent") } // Initialize storage service for file uploads var storageService *services.StorageService if cfg.Storage.UploadDir != "" { storageService, err = services.NewStorageService(&cfg.Storage) if err != nil { log.Warn().Err(err).Msg("Failed to initialize storage service - uploads disabled") } else { log.Info(). Str("upload_dir", cfg.Storage.UploadDir). Str("base_url", cfg.Storage.BaseURL). Int64("max_file_size", cfg.Storage.MaxFileSize). Msg("Storage service initialized") } } // Initialize PDF service for report generation pdfService := services.NewPDFService() log.Info().Msg("PDF service initialized") // Initialize push notification client (APNs + FCM) var pushClient *push.Client pushClient, err = push.NewClient(&cfg.Push) if err != nil { log.Warn().Err(err).Msg("Failed to initialize push client - push notifications disabled") } else { log.Info(). Bool("ios_enabled", pushClient.IsIOSEnabled()). Bool("android_enabled", pushClient.IsAndroidEnabled()). Msg("Push notification client initialized") } // Setup router with dependencies (includes admin panel at /admin) deps := &router.Dependencies{ DB: db, Cache: cache, Config: cfg, EmailService: emailService, PDFService: pdfService, PushClient: pushClient, StorageService: storageService, } r := router.SetupRouter(deps) // Create HTTP server srv := &http.Server{ Addr: fmt.Sprintf(":%d", cfg.Server.Port), Handler: r, ReadTimeout: 30 * time.Second, WriteTimeout: 30 * time.Second, IdleTimeout: 60 * time.Second, } // Start server in goroutine go func() { log.Info(). Str("addr", srv.Addr). Msg("HTTP server listening") if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatal().Err(err).Msg("Failed to start HTTP server") } }() // Wait for interrupt signal for graceful shutdown quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Info().Msg("Shutting down server...") // Graceful shutdown with timeout ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal().Err(err).Msg("Server forced to shutdown") } log.Info().Msg("Server exited") }