package app import ( "context" "fmt" "net/http" "time" "git.linuxforward.com/byom/byom-trends/config" "git.linuxforward.com/byom/byom-trends/handlers" "git.linuxforward.com/byom/byom-trends/logger" "git.linuxforward.com/byom/byom-trends/services/google" "git.linuxforward.com/byom/byom-trends/services/instagram" "git.linuxforward.com/byom/byom-trends/services/suggestions" "git.linuxforward.com/byom/byom-trends/services/tiktok" "git.linuxforward.com/byom/byom-trends/services/youtube" "git.linuxforward.com/byom/byom-trends/store" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/sirupsen/logrus" "gorm.io/driver/sqlite" "gorm.io/gorm" ) type App struct { entry *logrus.Entry cfg *config.Config ctx context.Context cancel context.CancelFunc dataStore *store.DataStore trendHandler *handlers.TrendHandler socialHandler *handlers.SocialHandler googleHandler *handlers.GoogleHandler suggestionsHandler *handlers.SuggestionsHandler router *gin.Engine httpServer *http.Server } func NewApp(ctx context.Context, cfg *config.Config) (*App, error) { entry := logger.NewLogger("app") // Initialize database db, err := gorm.Open(sqlite.Open(cfg.Database.Path), &gorm.Config{}) if err != nil { return nil, fmt.Errorf("open database: %w", err) } // Initialize services dataStore := store.NewDataStore(db) // Initialize external services instagramSvc := instagram.NewClient(&cfg.Social.Instagram) tiktokSvc := tiktok.NewClient(&cfg.Social.TikTok) youtubeSvc := youtube.NewClient(&cfg.Social.YouTube) googleSvc := google.NewClient(&cfg.Google) // Initialize handlers trendHandler := handlers.NewTrendHandler(dataStore, instagramSvc, tiktokSvc, youtubeSvc) socialHandler := handlers.NewSocialHandler(dataStore, instagramSvc, tiktokSvc, youtubeSvc) googleHandler := handlers.NewGoogleHandler(dataStore, googleSvc) // Initialize suggestions analyzer suggestionsAnalyzer, err := suggestions.NewAnalyzer(&cfg.LiteLLM) if err != nil { return nil, fmt.Errorf("create suggestions analyzer: %w", err) } suggestionsHandler := handlers.NewSuggestionsHandler(dataStore, suggestionsAnalyzer) // Initialize router router := gin.New() router.Use(gin.Recovery()) // Add CORS middleware if len(cfg.Server.CorsOrigins) > 0 { router.Use(cors.New(cors.Config{ AllowOrigins: cfg.Server.CorsOrigins, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"}, ExposeHeaders: []string{"Content-Length"}, AllowCredentials: true, MaxAge: 12 * time.Hour, })) } // Create app instance ctx, cancel := context.WithCancel(ctx) app := &App{ entry: entry, cfg: cfg, ctx: ctx, cancel: cancel, dataStore: dataStore, trendHandler: trendHandler, socialHandler: socialHandler, googleHandler: googleHandler, suggestionsHandler: suggestionsHandler, router: router, } // Setup routes app.setupRoutes() return app, nil } func (a *App) Start() error { a.httpServer = &http.Server{ Addr: fmt.Sprintf(":%d", a.cfg.Server.ListeningPort), Handler: a.router, } // Start HTTP server go func() { if err := a.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { a.entry.WithError(err).Error("failed to start server") } }() a.entry.Infof("Server started on port %d", a.cfg.Server.ListeningPort) return nil } func (a *App) Shutdown(ctx context.Context) error { a.entry.Info("Initiating graceful shutdown...") // Create a timeout context for shutdown ctx, cancel := context.WithTimeout(ctx, 30*time.Second) defer cancel() // Create a channel to signal completion done := make(chan bool, 1) go func() { // Close any active connections or resources if err := a.closeResources(); err != nil { a.entry.WithError(err).Error("Error closing resources") } done <- true }() // Wait for either completion or timeout select { case <-ctx.Done(): return fmt.Errorf("shutdown timed out: %v", ctx.Err()) case <-done: a.entry.Info("Graceful shutdown completed") } if a.httpServer != nil { if err := a.httpServer.Shutdown(ctx); err != nil { return fmt.Errorf("server shutdown: %w", err) } } // Cancel the main context if a.cancel != nil { a.cancel() } return nil } // closeResources handles the graceful closure of all resources func (a *App) closeResources() error { var errs []error // Close database connections if sqlDB, err := a.dataStore.GetDB().DB(); err == nil { if err := sqlDB.Close(); err != nil { errs = append(errs, fmt.Errorf("close database: %w", err)) } } // Close any other resources (e.g., message queues, cache connections) // Add other cleanup operations here if len(errs) > 0 { return fmt.Errorf("errors during resource cleanup: %v", errs) } return nil } func (a *App) setupRoutes() { // Add routes here api := a.router.Group("/api/v1/trends") // Trend analysis endpoints api.POST("/analyze/profile/:id", a.trendHandler.AnalyzeProfile) api.GET("/analysis/:id", a.trendHandler.GetTrendReport) // Social media endpoints api.POST("/social/connect", a.socialHandler.ConnectProfile) api.GET("/social/:platform/stats", a.socialHandler.GetProfileStats) // Google Trends endpoints api.POST("/google/analyze", a.googleHandler.AnalyzeGoogleTrends) // Add missing routes api.GET("/google/topics", a.googleHandler.GetRelatedTopics) api.GET("/google/regional", a.googleHandler.GetRegionalInterest) api.GET("/google/analysis/:id", a.googleHandler.GetTrendAnalysis) api.GET("/social/:platform/engagement/:id", a.socialHandler.GetProfileEngagement) // Suggestions endpoints api.POST("/suggestions", a.suggestionsHandler.GetSuggestions) api.GET("/suggestions/user/:user_id", a.suggestionsHandler.GetUserSuggestions) api.GET("/suggestions/:id", a.suggestionsHandler.GetSuggestion) }