123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207 |
- 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)
- }
|