package main import ( "context" "log" "net/http" "os" "os/signal" "syscall" "time" "github.com/breakpilot/ai-compliance-sdk/internal/api" "github.com/breakpilot/ai-compliance-sdk/internal/db" "github.com/breakpilot/ai-compliance-sdk/internal/llm" "github.com/breakpilot/ai-compliance-sdk/internal/rag" "github.com/gin-gonic/gin" "github.com/joho/godotenv" ) func main() { // Load environment variables if err := godotenv.Load(); err != nil { log.Println("No .env file found, using environment variables") } // Get configuration from environment port := getEnv("PORT", "8085") dbURL := getEnv("DATABASE_URL", "postgres://localhost:5432/sdk_states?sslmode=disable") qdrantURL := getEnv("QDRANT_URL", "http://localhost:6333") anthropicKey := getEnv("ANTHROPIC_API_KEY", "") // Initialize database connection dbPool, err := db.NewPostgresPool(dbURL) if err != nil { log.Printf("Warning: Database connection failed: %v", err) // Continue without database - use in-memory fallback } // Initialize RAG service ragService, err := rag.NewService(qdrantURL) if err != nil { log.Printf("Warning: RAG service initialization failed: %v", err) // Continue without RAG - will return empty results } // Initialize LLM service llmService := llm.NewService(anthropicKey) // Create Gin router gin.SetMode(gin.ReleaseMode) if os.Getenv("GIN_MODE") == "debug" { gin.SetMode(gin.DebugMode) } router := gin.Default() // CORS middleware router.Use(corsMiddleware()) // Health check router.GET("/health", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "healthy", "timestamp": time.Now().UTC().Format(time.RFC3339), "services": gin.H{ "database": dbPool != nil, "rag": ragService != nil, "llm": anthropicKey != "", }, }) }) // API routes v1 := router.Group("/sdk/v1") { // State Management stateHandler := api.NewStateHandler(dbPool) v1.GET("/state/:tenantId", stateHandler.GetState) v1.POST("/state", stateHandler.SaveState) v1.DELETE("/state/:tenantId", stateHandler.DeleteState) // RAG Search ragHandler := api.NewRAGHandler(ragService) v1.GET("/rag/search", ragHandler.Search) v1.GET("/rag/status", ragHandler.GetCorpusStatus) v1.POST("/rag/index", ragHandler.IndexDocument) // Document Generation generateHandler := api.NewGenerateHandler(llmService, ragService) v1.POST("/generate/dsfa", generateHandler.GenerateDSFA) v1.POST("/generate/tom", generateHandler.GenerateTOM) v1.POST("/generate/vvt", generateHandler.GenerateVVT) v1.POST("/generate/gutachten", generateHandler.GenerateGutachten) // Checkpoint Validation checkpointHandler := api.NewCheckpointHandler() v1.GET("/checkpoints", checkpointHandler.GetAll) v1.POST("/checkpoints/validate", checkpointHandler.Validate) // Academy (Compliance E-Learning) academyHandler := api.NewAcademyHandler(dbPool, llmService, ragService) academy := v1.Group("/academy") { // Course CRUD academy.GET("/courses", academyHandler.ListCourses) academy.GET("/courses/:id", academyHandler.GetCourse) academy.POST("/courses", academyHandler.CreateCourse) academy.PUT("/courses/:id", academyHandler.UpdateCourse) academy.DELETE("/courses/:id", academyHandler.DeleteCourse) // Statistics academy.GET("/statistics", academyHandler.GetStatistics) // Enrollments academy.GET("/enrollments", academyHandler.ListEnrollments) academy.POST("/enrollments", academyHandler.EnrollUser) academy.PUT("/enrollments/:id/progress", academyHandler.UpdateProgress) academy.POST("/enrollments/:id/complete", academyHandler.CompleteEnrollment) // Quiz academy.POST("/lessons/:id/quiz", academyHandler.SubmitQuiz) // Certificates academy.POST("/enrollments/:id/certificate", academyHandler.GenerateCertificateEndpoint) academy.GET("/certificates/:id", academyHandler.GetCertificate) academy.GET("/certificates/:id/pdf", academyHandler.DownloadCertificatePDF) // AI Course Generation academy.POST("/courses/generate", academyHandler.GenerateCourse) academy.POST("/lessons/:id/regenerate", academyHandler.RegenerateLesson) // Video Generation academy.POST("/courses/:id/generate-videos", academyHandler.GenerateVideos) academy.GET("/courses/:id/video-status", academyHandler.GetVideoStatus) } } // Create server srv := &http.Server{ Addr: ":" + port, Handler: router, } // Graceful shutdown go func() { log.Printf("SDK Backend starting on port %s", port) if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Failed to start server: %v", err) } }() // Wait for interrupt signal quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit log.Println("Shutting down server...") // Give outstanding requests 5 seconds to complete ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { log.Fatal("Server forced to shutdown:", err) } // Close database connection if dbPool != nil { dbPool.Close() } log.Println("Server exited") } func getEnv(key, defaultValue string) string { if value := os.Getenv(key); value != "" { return value } return defaultValue } func corsMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With, If-Match, If-None-Match") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE") c.Writer.Header().Set("Access-Control-Expose-Headers", "ETag, Last-Modified") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(204) return } c.Next() } }