package server import ( "context" "fmt" "log" "bugulma/backend/internal/analysis/regulatory" "bugulma/backend/internal/analysis/risk" "bugulma/backend/internal/analysis/transport" "bugulma/backend/internal/domain" "bugulma/backend/internal/financial" "bugulma/backend/internal/geospatial" "bugulma/backend/internal/graph" "bugulma/backend/internal/handler" "bugulma/backend/internal/matching" "bugulma/backend/internal/middleware" "bugulma/backend/internal/repository" "bugulma/backend/internal/routes" "bugulma/backend/internal/service" "bugulma/backend/pkg/config" "github.com/gin-gonic/gin" "github.com/joho/godotenv" "github.com/neo4j/neo4j-go-driver/v5/neo4j" "gorm.io/driver/postgres" "gorm.io/gorm" ) // StartServer initializes and starts the Bugulma backend server func StartServer(port string) error { // Load .env file if err := godotenv.Load(); err != nil { log.Printf("Warning: .env file not found, using environment variables") } // Load configuration cfg := config.Load() // Override port if provided if port != "" { cfg.ServerPort = port } // Initialize database db, err := initializeDatabase(cfg) if err != nil { return fmt.Errorf("failed to initialize database: %w", err) } // Initialize repositories userRepo, orgRepo, siteRepo, _, resourceFlowRepo, matchRepo, negotiationHistoryRepo, sharedAssetRepo, proposalRepo, geoFeatureRepo, productRepo, serviceRepo, communityListingRepo, publicTransportRepo := initializeRepositories(db) // Seed default data if err := seedDefaultData(userRepo); err != nil { log.Printf("Warning: Failed to seed default data: %v", err) } // (Public transport seeding will occur after services are initialized) // Initialize graph database (optional) graphSyncService, graphHandler, graphTraversalHandler, orgGraphRepo, siteGraphRepo, addressGraphRepo, flowGraphRepo, matchGraphRepo, sharedAssetGraphRepo := initializeGraphDatabase(cfg) // Initialize services orgService, siteService, resourceFlowService, geospatialService, sharedAssetService, analyticsService, authService, matchingSvc, cacheService, websocketService, aiService, proposalService, spatialMatcher, environmentalSvc, facilityOptimizer, economicService, userService, adminService, i18nService, subscriptionService, verificationService, productRepo, serviceRepo, communityListingRepo, publicTransportService := initializeServices( cfg, userRepo, orgRepo, siteRepo, resourceFlowRepo, matchRepo, negotiationHistoryRepo, sharedAssetRepo, proposalRepo, geoFeatureRepo, orgGraphRepo, siteGraphRepo, addressGraphRepo, flowGraphRepo, matchGraphRepo, sharedAssetGraphRepo, db, productRepo, serviceRepo, communityListingRepo, ) // Store geographical services for future API endpoints _ = spatialMatcher // Will be used in future API handlers _ = environmentalSvc // Will be used in future API handlers _ = facilityOptimizer // Will be used in future API handlers // Seed public transport data (if repository and imported files present) if publicTransportRepo != nil && publicTransportService != nil { if err := service.ImportPublicTransportData(context.Background(), publicTransportService, publicTransportRepo); err != nil { log.Printf("Warning: failed to import public transport data: %v", err) } } // Set graph repositories on services that need them if orgGraphRepo != nil { orgService.SetGraphRepository(orgGraphRepo) } // Initialize GTFS repository and schedule service gtfsRepo := repository.NewPublicTransportGTFSRepository(db) scheduleService := service.NewScheduleService(gtfsRepo, publicTransportRepo) // Initialize handlers (orgHandler is created inside initializeHandlers) orgHandler, siteHandler, resourceFlowHandler, proposalHandler, matchingHandler, authHandler, sharedAssetHandler, geospatialHandler, analyticsHandler, aiHandler, heritageHandler, userHandler, orgAdminHandler, i18nHandler, contentHandler, adminHandler, subscriptionHandler, discoveryHandler, publicTransportHandler := initializeHandlers( cfg, orgService, siteService, resourceFlowService, matchingSvc, authService, sharedAssetService, proposalService, geospatialService, analyticsService, aiService, cacheService, db, userService, adminService, i18nService, subscriptionService, verificationService, publicTransportService, scheduleService, ) // Setup router router := setupRouter(authService) // Initialize event-driven matching service with proper dependencies // Note: eventBus is created in initializeServices, but we need to recreate it here for event-driven service eventBus, err := service.NewRedisEventBus("redis://localhost:6379") if err != nil { log.Printf("Warning: Failed to create Redis event bus for event-driven matching: %v", err) eventBus = nil } eventDrivenMatchingService := service.NewEventDrivenMatchingService( eventBus, matchingSvc, resourceFlowRepo, matchRepo, websocketService.GetHub(), economicService, ) if eventBus != nil { if err := eventDrivenMatchingService.StartEventProcessing(context.Background()); err != nil { log.Printf("Warning: Failed to start event-driven matching: %v", err) } } // Setup routes setupRoutes(router, orgHandler, siteHandler, resourceFlowHandler, proposalHandler, matchingHandler, authHandler, sharedAssetHandler, geospatialHandler, analyticsHandler, aiHandler, heritageHandler, graphHandler, graphTraversalHandler, websocketService, orgService, siteService, geospatialService, graphSyncService, userHandler, orgAdminHandler, i18nHandler, contentHandler, adminHandler, subscriptionHandler, authService, discoveryHandler, publicTransportHandler) // Start server log.Printf("Server starting on port %s", cfg.ServerPort) if err := router.Run(fmt.Sprintf(":%s", cfg.ServerPort)); err != nil { return fmt.Errorf("failed to start server: %w", err) } return nil } // initializeDatabase sets up the database connection and runs migrations func initializeDatabase(cfg *config.Config) (*gorm.DB, error) { dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=%s", cfg.PostgresHost, cfg.PostgresPort, cfg.PostgresUser, cfg.PostgresPassword, cfg.PostgresDB, cfg.PostgresSSLMode, ) db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) if err != nil { return nil, fmt.Errorf("failed to connect to PostgreSQL: %w", err) } log.Println("Successfully connected to PostgreSQL database") // Run PostGIS-specific migrations FIRST (before AutoMigrate) if err := domain.RunPostGISMigrations(db); err != nil { log.Printf("Warning: Failed to run PostGIS migrations: %v", err) log.Println("Continuing without PostGIS spatial features") } else { log.Println("PostGIS migrations completed successfully") } // Run search migrations (pg_trgm extension and indexes) if err := domain.RunSearchMigrations(db); err != nil { log.Printf("Warning: Failed to run search migrations: %v", err) log.Println("Continuing without fuzzy search features") } else { log.Println("Search migrations completed successfully") } // Auto-migrate database schema if err := domain.AutoMigrate(db); err != nil { return nil, fmt.Errorf("failed to migrate database: %w", err) } // Create additional indexes after AutoMigrate if err := domain.CreateIndexes(db); err != nil { log.Printf("Warning: Failed to create indexes: %v", err) } else { log.Println("Indexes created successfully") } log.Println("Database migration completed successfully") return db, nil } // initializeRepositories creates all repository instances func initializeRepositories(db *gorm.DB) (domain.UserRepository, domain.OrganizationRepository, domain.SiteRepository, domain.AddressRepository, domain.ResourceFlowRepository, domain.MatchRepository, domain.NegotiationHistoryRepository, domain.SharedAssetRepository, domain.ProposalRepository, domain.GeographicalFeatureRepository, domain.ProductRepository, domain.ServiceRepository, domain.CommunityListingRepository, domain.PublicTransportRepository) { orgRepo := repository.NewOrganizationRepository(db) siteRepo := repository.NewSiteRepository(db) addressRepo := repository.NewAddressRepository(db) resourceFlowRepo := repository.NewResourceFlowRepository(db) matchRepo := repository.NewMatchRepository(db) negotiationHistoryRepo := repository.NewNegotiationHistoryRepository(db) sharedAssetRepo := repository.NewSharedAssetRepository(db) proposalRepo := repository.NewProposalRepository(db) userRepo := repository.NewUserRepository(db) geoFeatureRepo := repository.NewGeographicalFeatureRepository(db) productRepo := repository.NewProductRepository(db) serviceRepo := repository.NewServiceRepository(db) communityListingRepo := repository.NewCommunityListingRepository(db) publicTransportRepo := repository.NewPublicTransportRepository(db) _ = repository.NewResourceFlowVersionRepository(db) return userRepo, orgRepo, siteRepo, addressRepo, resourceFlowRepo, matchRepo, negotiationHistoryRepo, sharedAssetRepo, proposalRepo, geoFeatureRepo, productRepo, serviceRepo, communityListingRepo, publicTransportRepo } // seedDefaultData seeds initial data like admin user func seedDefaultData(userRepo domain.UserRepository) error { _, err := userRepo.GetByEmail(context.Background(), "admin@turash.dev") if err == nil { return nil } adminUser := &domain.User{ ID: "admin-1", Email: "admin@turash.dev", Name: "Admin User", Password: "$2a$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi", // "password" hashed with bcrypt Role: domain.UserRoleAdmin, } return userRepo.Create(context.Background(), adminUser) } // initializeGraphDatabase initializes Neo4j graph database if enabled func initializeGraphDatabase(cfg *config.Config) (*service.GraphSyncService, *handler.GraphHandler, *handler.GraphTraversalHandler, *repository.GraphOrganizationRepository, *repository.GraphSiteRepository, *repository.GraphAddressRepository, *repository.GraphResourceFlowRepository, *repository.GraphMatchRepository, *repository.GraphSharedAssetRepository) { if !cfg.Neo4jEnabled { return nil, nil, nil, nil, nil, nil, nil, nil, nil } neo4jURI := cfg.Neo4jURI if neo4jURI == "" { log.Println("Warning: Neo4j URI not configured, graph database disabled") return nil, nil, nil, nil, nil, nil, nil, nil, nil } auth := neo4j.BasicAuth(cfg.Neo4jUsername, cfg.Neo4jPassword, "") driver, err := neo4j.NewDriverWithContext(neo4jURI, auth) if err != nil { log.Printf("Warning: Failed to create Neo4j driver: %v", err) log.Println("Continuing without graph database features") return nil, nil, nil, nil, nil, nil, nil, nil, nil } // Verify connectivity ctx := context.Background() if err := driver.VerifyConnectivity(ctx); err != nil { log.Printf("Warning: Failed to connect to Neo4j: %v", err) log.Println("Continuing without graph database features") return nil, nil, nil, nil, nil, nil, nil, nil, nil } log.Println("Successfully connected to Neo4j graph database") // Initialize graph repositories orgGraphRepo := repository.NewGraphOrganizationRepository(driver, cfg.Neo4jDatabase) siteGraphRepo := repository.NewGraphSiteRepository(driver, cfg.Neo4jDatabase) addressGraphRepo := repository.NewGraphAddressRepository(driver, cfg.Neo4jDatabase) flowGraphRepo := repository.NewGraphResourceFlowRepository(driver, cfg.Neo4jDatabase) matchGraphRepo := repository.NewGraphMatchRepository(driver, cfg.Neo4jDatabase) sharedAssetGraphRepo := repository.NewGraphSharedAssetRepository(driver, cfg.Neo4jDatabase) productGraphRepo := repository.NewGraphProductRepository(driver, cfg.Neo4jDatabase) serviceGraphRepo := repository.NewGraphServiceRepository(driver, cfg.Neo4jDatabase) // Initialize graph sync service graphSyncService := service.NewGraphSyncService( orgGraphRepo, siteGraphRepo, addressGraphRepo, flowGraphRepo, matchGraphRepo, sharedAssetGraphRepo, productGraphRepo, serviceGraphRepo, ) // Initialize graph calculator for traversal service graphCalculator := graph.NewCalculatorWithDefaults(driver, cfg.Neo4jDatabase) // Initialize services graphTraversalService := service.NewGraphTraversalService(graphCalculator) // Initialize handlers graphHandler := handler.NewGraphHandler(driver, cfg.Neo4jDatabase, graphSyncService) graphTraversalHandler := handler.NewGraphTraversalHandler(graphTraversalService) return graphSyncService, graphHandler, graphTraversalHandler, orgGraphRepo, siteGraphRepo, addressGraphRepo, flowGraphRepo, matchGraphRepo, sharedAssetGraphRepo } // initializeServices creates all service instances func initializeServices( cfg *config.Config, userRepo domain.UserRepository, orgRepo domain.OrganizationRepository, siteRepo domain.SiteRepository, resourceFlowRepo domain.ResourceFlowRepository, matchRepo domain.MatchRepository, negotiationHistoryRepo domain.NegotiationHistoryRepository, sharedAssetRepo domain.SharedAssetRepository, proposalRepo domain.ProposalRepository, geoFeatureRepo domain.GeographicalFeatureRepository, orgGraphRepo *repository.GraphOrganizationRepository, siteGraphRepo *repository.GraphSiteRepository, addressGraphRepo *repository.GraphAddressRepository, flowGraphRepo *repository.GraphResourceFlowRepository, matchGraphRepo *repository.GraphMatchRepository, sharedAssetGraphRepo *repository.GraphSharedAssetRepository, db *gorm.DB, productRepo domain.ProductRepository, serviceRepo domain.ServiceRepository, communityListingRepo domain.CommunityListingRepository, ) (*service.OrganizationService, *service.SiteService, *service.ResourceFlowService, *service.GeospatialService, *service.SharedAssetService, *service.AnalyticsService, *service.AuthService, *matching.Service, service.CacheService, *service.WebSocketService, *service.AIService, *service.ProposalService, *service.SpatialResourceMatcher, *service.EnvironmentalImpactService, *service.FacilityLocationOptimizer, *service.EconomicService, *service.UserService, *service.AdminService, *service.I18nService, *service.SubscriptionService, *service.VerificationService, domain.ProductRepository, domain.ServiceRepository, domain.CommunityListingRepository, *service.PublicTransportService) { // Create event bus first eventBus, err := service.NewRedisEventBus("redis://localhost:6379") if err != nil { log.Printf("Warning: Failed to create Redis event bus, using in-memory: %v", err) eventBus = nil } // Create services orgService := service.NewOrganizationService(orgRepo, nil) // Graph repo set later if available siteService := service.NewSiteService(siteRepo) resourceFlowService := service.NewResourceFlowService(resourceFlowRepo, eventBus) authService := service.NewAuthService(userRepo, cfg.JWTSecret) sharedAssetService := service.NewSharedAssetService(sharedAssetRepo) geospatialService := service.NewGeospatialService(db, geoFeatureRepo) analyticsService := service.NewAnalyticsService(db, orgRepo, siteRepo, resourceFlowRepo, matchRepo, sharedAssetRepo) proposalService := service.NewProposalService(proposalRepo) // Initialize geospatial calculator geoCalc := geospatial.NewCalculatorWithDefaults() // Initialize geographical transportation service geoTransportSvc := service.NewTransportationService(geoCalc) // Initialize geographical services spatialMatcher := service.NewSpatialResourceMatcher(geoFeatureRepo, siteRepo, resourceFlowRepo, geospatialService, geoTransportSvc, geoCalc) environmentalSvc := service.NewEnvironmentalImpactService(geoFeatureRepo, siteRepo, geospatialService, geoCalc) facilityOptimizer := service.NewFacilityLocationOptimizer(geoFeatureRepo, siteRepo, geospatialService, spatialMatcher, environmentalSvc, geoTransportSvc) // Create cache and websocket services var cacheService service.CacheService redisCache, err := service.NewRedisCacheService(cfg.RedisURL) if err != nil { log.Printf("Warning: Failed to initialize Redis cache, using in-memory cache: %v", err) cacheService = service.NewMemoryCacheService() } else { log.Println("Successfully initialized Redis cache service") cacheService = redisCache } websocketService := service.NewWebSocketService() // Create analysis services for matching riskSvc := risk.NewService() transportSvc := transport.NewService() regulatorySvc := regulatory.NewService() // Create matching service matchingSvc := matching.NewService( matchRepo, negotiationHistoryRepo, resourceFlowRepo, siteRepo, orgRepo, productRepo, serviceRepo, communityListingRepo, riskSvc, transportSvc, regulatorySvc, eventBus, ) // Create financial calculator and economic service financialConfig := financial.DefaultConfig() financialCalculator := financial.NewCalculator(financialConfig) economicService := service.NewEconomicService(financialCalculator) aiService := service.NewAIService() // Create additional services needed by handlers userService := service.NewUserService(userRepo) // Create trust service trustRepo := repository.NewTrustMetricsRepository(db) verifiedRepo := repository.NewVerifiedDataRepository(db) historicalRepo := repository.NewHistoricalSuccessRepository(db) trustService := service.NewTrustService(trustRepo, verifiedRepo, historicalRepo) // Create i18n service - simplified initialization locRepo := repository.NewLocalizationRepository(db) i18nService := service.NewI18nService(nil, locRepo, nil, nil) // Simplified - can be enhanced later // Create subscription service subscriptionRepo := repository.NewSubscriptionRepository(db) // UsageTrackingRepository can be nil for now - create if needed later subscriptionService := service.NewSubscriptionService(subscriptionRepo, nil, userRepo) // Create verification service verificationService := service.NewVerificationService(trustService, orgService) // Create admin service adminService := service.NewAdminService(orgService, userService, verificationService, i18nService) // Initialize public transport service (reads data/bugulma_public_transport_enriched.json if available) publicTransportService, err := service.NewPublicTransportService("data") if err != nil { log.Printf("Warning: failed to initialize public transport service: %v", err) publicTransportService = nil } return orgService, siteService, resourceFlowService, geospatialService, sharedAssetService, analyticsService, authService, matchingSvc, cacheService, websocketService, aiService, proposalService, spatialMatcher, environmentalSvc, facilityOptimizer, economicService, userService, adminService, i18nService, subscriptionService, verificationService, productRepo, serviceRepo, communityListingRepo, publicTransportService } // initializeHandlers creates all HTTP handler instances func initializeHandlers( cfg *config.Config, orgService *service.OrganizationService, siteService *service.SiteService, resourceFlowService *service.ResourceFlowService, matchingSvc *matching.Service, authService *service.AuthService, sharedAssetService *service.SharedAssetService, proposalService *service.ProposalService, geospatialService *service.GeospatialService, analyticsService *service.AnalyticsService, aiService *service.AIService, cacheService service.CacheService, db *gorm.DB, userService *service.UserService, adminService *service.AdminService, i18nService *service.I18nService, subscriptionService *service.SubscriptionService, verificationService *service.VerificationService, publicTransportService *service.PublicTransportService, scheduleService *service.ScheduleService, ) (*handler.OrganizationHandler, *handler.SiteHandler, *handler.ResourceFlowHandler, *handler.ProposalHandler, *handler.MatchingHandler, *handler.AuthHandler, *handler.SharedAssetHandler, *handler.GeospatialHandler, *handler.AnalyticsHandler, *handler.AIHandler, *handler.HeritageHandler, *handler.UserHandler, *handler.OrganizationAdminHandler, *handler.I18nHandler, *handler.ContentHandler, *handler.AdminHandler, *handler.SubscriptionHandler, *handler.DiscoveryHandler, *handler.PublicTransportHandler) { imageService := service.NewImageService(cfg) orgHandler := handler.NewOrganizationHandler(orgService, imageService, resourceFlowService, matchingSvc, proposalService) siteHandler := handler.NewSiteHandler(siteService) resourceFlowHandler := handler.NewResourceFlowHandler(resourceFlowService) proposalHandler := handler.NewProposalHandler(proposalService) matchingHandler := handler.NewMatchingHandler(matchingSvc, nil) authHandler := handler.NewAuthHandler(authService) sharedAssetHandler := handler.NewSharedAssetHandler(sharedAssetService) geospatialHandler := handler.NewGeospatialHandler(geospatialService) analyticsHandler := handler.NewAnalyticsHandler(analyticsService) aiHandler := handler.NewAIHandler(aiService) heritageHandler := handler.NewHeritageHandler(repository.NewHeritageRepository(db)) // Additional handlers userHandler := handler.NewUserHandler(userService) orgAdminHandler := handler.NewOrganizationAdminHandler(orgHandler, verificationService) i18nHandler := handler.NewI18nHandler(i18nService) contentHandler := handler.NewContentHandler(service.NewContentService( repository.NewStaticPageRepository(db), repository.NewAnnouncementRepository(db), repository.NewMediaAssetRepository(db), )) adminHandler := handler.NewAdminHandler(adminService) subscriptionHandler := handler.NewSubscriptionHandler(subscriptionService) discoveryHandler := handler.NewDiscoveryHandler(matchingSvc) publicTransportHandler := handler.NewPublicTransportHandler(publicTransportService, scheduleService) return orgHandler, siteHandler, resourceFlowHandler, proposalHandler, matchingHandler, authHandler, sharedAssetHandler, geospatialHandler, analyticsHandler, aiHandler, heritageHandler, userHandler, orgAdminHandler, i18nHandler, contentHandler, adminHandler, subscriptionHandler, discoveryHandler, publicTransportHandler } // setupRouter configures the Gin router with middleware func setupRouter(authService *service.AuthService) *gin.Engine { router := gin.Default() // Serve static files router.Static("/static", "./static") // CORS middleware router.Use(func(c *gin.Context) { c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() }) // Context middleware for extracting user/org info from JWT or headers router.Use(middleware.ContextMiddleware(authService)) return router } // setupRoutes configures all API routes using the organized routes package func setupRoutes( router *gin.Engine, orgHandler *handler.OrganizationHandler, siteHandler *handler.SiteHandler, resourceHandler *handler.ResourceFlowHandler, proposalHandler *handler.ProposalHandler, matchingHandler *handler.MatchingHandler, authHandler *handler.AuthHandler, sharedAssetHandler *handler.SharedAssetHandler, geospatialHandler *handler.GeospatialHandler, analyticsHandler *handler.AnalyticsHandler, aiHandler *handler.AIHandler, heritageHandler *handler.HeritageHandler, graphHandler *handler.GraphHandler, graphTraversalHandler *handler.GraphTraversalHandler, websocketService *service.WebSocketService, orgService *service.OrganizationService, siteService *service.SiteService, geospatialService *service.GeospatialService, graphSyncService *service.GraphSyncService, userHandler *handler.UserHandler, orgAdminHandler *handler.OrganizationAdminHandler, i18nHandler *handler.I18nHandler, contentHandler *handler.ContentHandler, adminHandler *handler.AdminHandler, subscriptionHandler *handler.SubscriptionHandler, authService *service.AuthService, discoveryHandler *handler.DiscoveryHandler, publicTransportHandler *handler.PublicTransportHandler, ) { routes.RegisterAllRoutes( router, orgHandler, siteHandler, resourceHandler, proposalHandler, matchingHandler, authHandler, sharedAssetHandler, geospatialHandler, analyticsHandler, aiHandler, heritageHandler, graphHandler, graphTraversalHandler, websocketService, userHandler, orgAdminHandler, i18nHandler, contentHandler, adminHandler, subscriptionHandler, authService, discoveryHandler, publicTransportHandler, ) }