turash/bugulma/backend/internal/server/server.go
Damir Mukimov 02fad6713c refactor(docs/locales): rename project from 'Tуган Як'/'Tugan Yak' to 'Turash' across docs, locales and test fixtures
- Update locales (ru, tt, en) to use 'Turash' and 'Turash AI'
- Update metadata, index.html, and pixel-art README
- Replace example credentials/emails from @tuganyak.dev -> @turash.dev
- Update admin defaults and migration seed to use new admin@turash.dev
- Update docs mentioning the old name
2025-12-15 05:42:16 +01:00

540 lines
24 KiB
Go

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