package app import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/breakpilot/ai-compliance-sdk/internal/academy" "github.com/breakpilot/ai-compliance-sdk/internal/api/handlers" "github.com/breakpilot/ai-compliance-sdk/internal/audit" "github.com/breakpilot/ai-compliance-sdk/internal/config" "github.com/breakpilot/ai-compliance-sdk/internal/iace" "github.com/breakpilot/ai-compliance-sdk/internal/llm" "github.com/breakpilot/ai-compliance-sdk/internal/portfolio" "github.com/breakpilot/ai-compliance-sdk/internal/rbac" "github.com/breakpilot/ai-compliance-sdk/internal/roadmap" "github.com/breakpilot/ai-compliance-sdk/internal/training" "github.com/breakpilot/ai-compliance-sdk/internal/ucca" "github.com/breakpilot/ai-compliance-sdk/internal/whistleblower" "github.com/breakpilot/ai-compliance-sdk/internal/workshop" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgxpool" ) // Run initializes and starts the AI Compliance SDK server. func Run() { cfg, err := config.Load() if err != nil { log.Fatalf("Failed to load configuration: %v", err) } if cfg.IsProduction() { gin.SetMode(gin.ReleaseMode) } ctx := context.Background() pool, err := pgxpool.New(ctx, cfg.DatabaseURL) if err != nil { log.Fatalf("Failed to connect to database: %v", err) } defer pool.Close() if err := pool.Ping(ctx); err != nil { log.Fatalf("Failed to ping database: %v", err) } log.Println("Connected to database") router := buildRouter(cfg, pool) srv := &http.Server{ Addr: ":" + cfg.Port, Handler: router, ReadTimeout: 30 * time.Second, WriteTimeout: 5 * time.Minute, IdleTimeout: 60 * time.Second, } go func() { log.Printf("AI Compliance SDK starting on port %s", cfg.Port) log.Printf("Environment: %s", cfg.Environment) log.Printf("Primary LLM Provider: %s", cfg.LLMProvider) log.Printf("Fallback LLM Provider: %s", cfg.LLMFallbackProvider) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed: %v", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatalf("Server forced to shutdown: %v", err) } log.Println("Server exited") } // buildRouter wires all stores, services, and handlers onto a new Gin engine. func buildRouter(cfg *config.Config, pool *pgxpool.Pool) *gin.Engine { // Stores rbacStore := rbac.NewStore(pool) auditStore := audit.NewStore(pool) uccaStore := ucca.NewStore(pool) escalationStore := ucca.NewEscalationStore(pool) corpusVersionStore := ucca.NewCorpusVersionStore(pool) roadmapStore := roadmap.NewStore(pool) workshopStore := workshop.NewStore(pool) portfolioStore := portfolio.NewStore(pool) academyStore := academy.NewStore(pool) whistleblowerStore := whistleblower.NewStore(pool) iaceStore := iace.NewStore(pool) trainingStore := training.NewStore(pool) obligationsStore := ucca.NewObligationsStore(pool) // Services rbacService := rbac.NewService(rbacStore) policyEngine := rbac.NewPolicyEngine(rbacService, rbacStore) // LLM providers providerRegistry := llm.NewProviderRegistry(cfg.LLMProvider, cfg.LLMFallbackProvider) providerRegistry.Register(llm.NewOllamaAdapter(cfg.OllamaURL, cfg.OllamaDefaultModel)) if cfg.AnthropicAPIKey != "" { providerRegistry.Register(llm.NewAnthropicAdapter(cfg.AnthropicAPIKey, cfg.AnthropicDefaultModel)) } piiDetector := llm.NewPIIDetectorWithPatterns(llm.AllPIIPatterns()) ttsClient := training.NewTTSClient(cfg.TTSServiceURL) contentGenerator := training.NewContentGenerator(providerRegistry, piiDetector, trainingStore, ttsClient) accessGate := llm.NewAccessGate(policyEngine, piiDetector, providerRegistry) trailBuilder := audit.NewTrailBuilder(auditStore) exporter := audit.NewExporter(auditStore) blockGenerator := training.NewBlockGenerator(trainingStore, contentGenerator) // Handlers rbacHandlers := handlers.NewRBACHandlers(rbacStore, rbacService, policyEngine) llmHandlers := handlers.NewLLMHandlers(accessGate, providerRegistry, piiDetector, auditStore, trailBuilder) auditHandlers := handlers.NewAuditHandlers(auditStore, exporter) uccaHandlers := handlers.NewUCCAHandlers(uccaStore, escalationStore, providerRegistry) escalationHandlers := handlers.NewEscalationHandlers(escalationStore, uccaStore) roadmapHandlers := handlers.NewRoadmapHandlers(roadmapStore) workshopHandlers := handlers.NewWorkshopHandlers(workshopStore) portfolioHandlers := handlers.NewPortfolioHandlers(portfolioStore) academyHandlers := handlers.NewAcademyHandlers(academyStore, trainingStore) whistleblowerHandlers := handlers.NewWhistleblowerHandlers(whistleblowerStore) iaceHandler := handlers.NewIACEHandler(iaceStore, providerRegistry) trainingHandlers := handlers.NewTrainingHandlers(trainingStore, contentGenerator, blockGenerator, ttsClient) ragHandlers := handlers.NewRAGHandlers(corpusVersionStore) obligationsHandlers := handlers.NewObligationsHandlersWithStore(obligationsStore) rbacMiddleware := rbac.NewMiddleware(rbacService, policyEngine) // Router router := gin.Default() router.Use(cors.New(cors.Config{ AllowOrigins: cfg.AllowedOrigins, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Origin", "Content-Type", "Authorization", "X-User-ID", "X-Tenant-ID", "X-Namespace-ID", "X-Tenant-Slug"}, ExposeHeaders: []string{"Content-Length", "Content-Disposition"}, AllowCredentials: true, MaxAge: 12 * time.Hour, })) router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "timestamp": time.Now().UTC().Format(time.RFC3339), }) }) registerRoutes(router, rbacMiddleware, rbacHandlers, llmHandlers, auditHandlers, uccaHandlers, escalationHandlers, obligationsHandlers, ragHandlers, roadmapHandlers, workshopHandlers, portfolioHandlers, academyHandlers, trainingHandlers, whistleblowerHandlers, iaceHandler) return router }