server.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. package app
  2. import (
  3. "context"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "git.linuxforward.com/byom/byom-trends/config"
  8. "git.linuxforward.com/byom/byom-trends/handlers"
  9. "git.linuxforward.com/byom/byom-trends/logger"
  10. "git.linuxforward.com/byom/byom-trends/services/google"
  11. "git.linuxforward.com/byom/byom-trends/services/instagram"
  12. "git.linuxforward.com/byom/byom-trends/services/suggestions"
  13. "git.linuxforward.com/byom/byom-trends/services/tiktok"
  14. "git.linuxforward.com/byom/byom-trends/services/youtube"
  15. "git.linuxforward.com/byom/byom-trends/store"
  16. "github.com/gin-contrib/cors"
  17. "github.com/gin-gonic/gin"
  18. "github.com/sirupsen/logrus"
  19. "gorm.io/driver/sqlite"
  20. "gorm.io/gorm"
  21. )
  22. type App struct {
  23. entry *logrus.Entry
  24. cfg *config.Config
  25. ctx context.Context
  26. cancel context.CancelFunc
  27. dataStore *store.DataStore
  28. trendHandler *handlers.TrendHandler
  29. socialHandler *handlers.SocialHandler
  30. googleHandler *handlers.GoogleHandler
  31. suggestionsHandler *handlers.SuggestionsHandler
  32. router *gin.Engine
  33. httpServer *http.Server
  34. }
  35. func NewApp(ctx context.Context, cfg *config.Config) (*App, error) {
  36. entry := logger.NewLogger("app")
  37. // Initialize database
  38. db, err := gorm.Open(sqlite.Open(cfg.Database.Path), &gorm.Config{})
  39. if err != nil {
  40. return nil, fmt.Errorf("open database: %w", err)
  41. }
  42. // Initialize services
  43. dataStore := store.NewDataStore(db)
  44. // Initialize external services
  45. instagramSvc := instagram.NewClient(&cfg.Social.Instagram)
  46. tiktokSvc := tiktok.NewClient(&cfg.Social.TikTok)
  47. youtubeSvc := youtube.NewClient(&cfg.Social.YouTube)
  48. googleSvc := google.NewClient(&cfg.Google)
  49. // Initialize handlers
  50. trendHandler := handlers.NewTrendHandler(dataStore, instagramSvc, tiktokSvc, youtubeSvc)
  51. socialHandler := handlers.NewSocialHandler(dataStore, instagramSvc, tiktokSvc, youtubeSvc)
  52. googleHandler := handlers.NewGoogleHandler(dataStore, googleSvc)
  53. // Initialize suggestions analyzer
  54. suggestionsAnalyzer, err := suggestions.NewAnalyzer(&cfg.LiteLLM)
  55. if err != nil {
  56. return nil, fmt.Errorf("create suggestions analyzer: %w", err)
  57. }
  58. suggestionsHandler := handlers.NewSuggestionsHandler(dataStore, suggestionsAnalyzer)
  59. // Initialize router
  60. router := gin.New()
  61. router.Use(gin.Recovery())
  62. // Add CORS middleware
  63. if len(cfg.Server.CorsOrigins) > 0 {
  64. router.Use(cors.New(cors.Config{
  65. AllowOrigins: cfg.Server.CorsOrigins,
  66. AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
  67. AllowHeaders: []string{"Origin", "Content-Type", "Accept", "Authorization"},
  68. ExposeHeaders: []string{"Content-Length"},
  69. AllowCredentials: true,
  70. MaxAge: 12 * time.Hour,
  71. }))
  72. }
  73. // Create app instance
  74. ctx, cancel := context.WithCancel(ctx)
  75. app := &App{
  76. entry: entry,
  77. cfg: cfg,
  78. ctx: ctx,
  79. cancel: cancel,
  80. dataStore: dataStore,
  81. trendHandler: trendHandler,
  82. socialHandler: socialHandler,
  83. googleHandler: googleHandler,
  84. suggestionsHandler: suggestionsHandler,
  85. router: router,
  86. }
  87. // Setup routes
  88. app.setupRoutes()
  89. return app, nil
  90. }
  91. func (a *App) Start() error {
  92. a.httpServer = &http.Server{
  93. Addr: fmt.Sprintf(":%d", a.cfg.Server.ListeningPort),
  94. Handler: a.router,
  95. }
  96. // Start HTTP server
  97. go func() {
  98. if err := a.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  99. a.entry.WithError(err).Error("failed to start server")
  100. }
  101. }()
  102. a.entry.Infof("Server started on port %d", a.cfg.Server.ListeningPort)
  103. return nil
  104. }
  105. func (a *App) Shutdown(ctx context.Context) error {
  106. a.entry.Info("Initiating graceful shutdown...")
  107. // Create a timeout context for shutdown
  108. ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
  109. defer cancel()
  110. // Create a channel to signal completion
  111. done := make(chan bool, 1)
  112. go func() {
  113. // Close any active connections or resources
  114. if err := a.closeResources(); err != nil {
  115. a.entry.WithError(err).Error("Error closing resources")
  116. }
  117. done <- true
  118. }()
  119. // Wait for either completion or timeout
  120. select {
  121. case <-ctx.Done():
  122. return fmt.Errorf("shutdown timed out: %v", ctx.Err())
  123. case <-done:
  124. a.entry.Info("Graceful shutdown completed")
  125. }
  126. if a.httpServer != nil {
  127. if err := a.httpServer.Shutdown(ctx); err != nil {
  128. return fmt.Errorf("server shutdown: %w", err)
  129. }
  130. }
  131. // Cancel the main context
  132. if a.cancel != nil {
  133. a.cancel()
  134. }
  135. return nil
  136. }
  137. // closeResources handles the graceful closure of all resources
  138. func (a *App) closeResources() error {
  139. var errs []error
  140. // Close database connections
  141. if sqlDB, err := a.dataStore.GetDB().DB(); err == nil {
  142. if err := sqlDB.Close(); err != nil {
  143. errs = append(errs, fmt.Errorf("close database: %w", err))
  144. }
  145. }
  146. // Close any other resources (e.g., message queues, cache connections)
  147. // Add other cleanup operations here
  148. if len(errs) > 0 {
  149. return fmt.Errorf("errors during resource cleanup: %v", errs)
  150. }
  151. return nil
  152. }
  153. func (a *App) setupRoutes() {
  154. // Add routes here
  155. api := a.router.Group("/api/v1/trends")
  156. // Trend analysis endpoints
  157. api.POST("/analyze/profile/:id", a.trendHandler.AnalyzeProfile)
  158. api.GET("/analysis/:id", a.trendHandler.GetTrendReport)
  159. // Social media endpoints
  160. api.POST("/social/connect", a.socialHandler.ConnectProfile)
  161. api.GET("/social/:platform/stats", a.socialHandler.GetProfileStats)
  162. // Google Trends endpoints
  163. api.POST("/google/analyze", a.googleHandler.AnalyzeGoogleTrends)
  164. // Add missing routes
  165. api.GET("/google/topics", a.googleHandler.GetRelatedTopics)
  166. api.GET("/google/regional", a.googleHandler.GetRegionalInterest)
  167. api.GET("/google/analysis/:id", a.googleHandler.GetTrendAnalysis)
  168. api.GET("/social/:platform/engagement/:id", a.socialHandler.GetProfileEngagement)
  169. // Suggestions endpoints
  170. api.POST("/suggestions", a.suggestionsHandler.GetSuggestions)
  171. api.GET("/suggestions/user/:user_id", a.suggestionsHandler.GetUserSuggestions)
  172. api.GET("/suggestions/:id", a.suggestionsHandler.GetSuggestion)
  173. }