package api import ( "context" "log" "net/http" "time" "containr/internal/build" "containr/internal/config" "containr/internal/database" "containr/internal/deployment" "containr/internal/docker" "containr/internal/ha" "containr/internal/metrics" "containr/internal/middleware" "containr/internal/scaling" "github.com/gin-gonic/gin" "gorm.io/driver/postgres" "gorm.io/gorm" ) func SetupRoutes(router *gin.Engine, db *database.DB, redis *database.Redis, cfg *config.Config) { // Expose Better Auth through backend so frontend can use a single backend origin. setupAuthProxyRoutes(router, cfg) // Initialize Docker client (non-fatal if it fails) var dockerClient *docker.Client var buildManager *build.BuildManager var deploymentEngine *deployment.DeploymentEngine if client, err := docker.NewClient(); err != nil { log.Printf("Warning: Failed to initialize Docker client: %v", err) log.Printf("Docker-related features will be disabled") } else { dockerClient = client buildManager = build.NewBuildManager("/tmp/containr-builds", dockerClient) deploymentEngine = deployment.NewDeploymentEngine(buildManager, dockerClient) } // Initialize build handler buildHandler := NewBuildHandler(buildManager, dockerClient, db) // Initialize scheduler and metrics systems scheduler := deployment.NewScheduler() var metricsStorage metrics.MetricsStorage = metrics.NewInMemoryMetricsStorage() if db != nil && db.DB != nil { metricsStorage = metrics.NewPostgreSQLMetricsStorage(db.DB) } metricsCollector := metrics.NewMetricsCollector(scheduler, metricsStorage) autoScaler := scaling.NewAutoScaler(scheduler, metricsCollector) haManager := ha.NewHighAvailabilityManager(scheduler, metricsCollector) haAPIManager := NewHAManager(haManager) // Initialize scaling handler scalingHandler := NewScalingHandler(autoScaler) // Initialize GORM for agent system gormDB, err := gorm.Open(postgres.Open(cfg.DatabaseURL), &gorm.Config{}) if err != nil { panic("Failed to initialize GORM: " + err.Error()) } // Initialize agent handler agentHandler := NewNodeAgentHandler(gormDB) // Initialize database handler databaseHandler := NewDatabaseHandler(db.DB, dockerClient) // Initialize security handler securityHandler := NewSecurityHandler(db, cfg.JWTSecret) // Note: Proxmox integration can be added later if needed // For now, focusing on core Containr and APwhy functionality // Add database and JWT secret to gin context for handlers router.Use(func(c *gin.Context) { c.Set("db", db) c.Set("redis", redis) c.Set("jwt_secret", cfg.JWTSecret) c.Set("docker_client", dockerClient) c.Set("database_handler", databaseHandler) c.Set("build_manager", buildManager) if deploymentEngine != nil { c.Set("deployment_engine", deploymentEngine) } c.Set("scheduler", scheduler) c.Set("metrics_collector", metricsCollector) c.Set("auto_scaler", autoScaler) c.Set("ha_manager", haManager) c.Set("scaling_handler", scalingHandler) c.Set("gorm_db", gormDB) c.Next() }) go func() { if err := haManager.Start(context.Background()); err != nil { log.Printf("HA manager exited: %v", err) } }() // Health check endpoint router.GET("/live", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "ok", "service": "containr-api", }) }) router.HEAD("/live", func(c *gin.Context) { c.Status(http.StatusOK) }) healthHandler := func(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 2*time.Second) defer cancel() databaseStatus := "ok" redisStatus := "ok" checks := gin.H{ "database": databaseStatus, "redis": redisStatus, } overallStatus := "ok" statusCode := http.StatusOK if err := db.Health(ctx); err != nil { databaseStatus = "unhealthy" checks["database"] = databaseStatus checks["databaseError"] = err.Error() overallStatus = "degraded" statusCode = http.StatusServiceUnavailable } if redis == nil { redisStatus = "unhealthy" checks["redis"] = redisStatus checks["redisError"] = "redis client not initialized" overallStatus = "degraded" statusCode = http.StatusServiceUnavailable } else if err := redis.Health(ctx); err != nil { redisStatus = "unhealthy" checks["redis"] = redisStatus checks["redisError"] = err.Error() overallStatus = "degraded" statusCode = http.StatusServiceUnavailable } c.JSON(statusCode, gin.H{ "status": overallStatus, "service": "containr-api", "checks": checks, }) } router.GET("/health", healthHandler) router.HEAD("/health", healthHandler) router.GET("/ready", healthHandler) router.HEAD("/ready", healthHandler) // API v1 routes v1 := router.Group("/api/v1") { // Public routes (no authentication required) public := v1.Group("/") { public.POST("/auth/login", handleLogin) public.POST("/auth/register", handleRegister) } // Protected routes (authentication required) protected := v1.Group("/") protected.Use(middleware.Auth(cfg.JWTSecret)) { // User routes protected.GET("/user/profile", handleGetProfile) protected.PUT("/user/profile", handleUpdateProfile) // Project routes protected.GET("/projects", handleGetProjects) protected.POST("/projects", handleCreateProject) // Service routes (nested under projects) protected.GET("/projects/:id/services", handleGetServices) protected.POST("/projects/:id/services", handleCreateService) // Generic project routes protected.GET("/projects/:id", handleGetProject) protected.PUT("/projects/:id", handleUpdateProject) protected.DELETE("/projects/:id", handleDeleteProject) // Service routes protected.GET("/services/:id", handleGetService) protected.PUT("/services/:id", handleUpdateService) protected.DELETE("/services/:id", handleDeleteService) // Deployment routes protected.GET("/services/:id/deployments", handleGetDeployments) protected.POST("/services/:id/deployments", handleCreateDeployment) protected.GET("/deployments/:id", handleGetDeployment) protected.POST("/deployments/:id/rollback", handleRollbackDeployment) // Environment variables routes protected.GET("/services/:id/variables", handleGetVariables) protected.PUT("/services/:id/variables", handleUpdateVariables) // Logs routes protected.GET("/services/:id/logs", handleGetLogs) protected.GET("/deployments/:id/logs", handleGetDeploymentLogs) // Git integration routes protected.GET("/git/github-app/install-url", handleGetGitHubAppInstallURL) protected.POST("/git/github-app/connect", handleConnectGitHubApp) protected.GET("/git/providers", handleGetGitProviders) protected.POST("/git/providers", handleCreateGitProvider) protected.GET("/git/providers/:providerId/repositories", handleGetGitRepositories) protected.POST("/git/repositories/connect", handleConnectGitRepository) protected.GET("/git/repositories", handleGetConnectedRepositories) protected.POST("/git/webhooks", handleCreateWebhook) // Build routes protected.POST("/builds", buildHandler.StartBuild) protected.GET("/builds", buildHandler.ListBuilds) protected.GET("/builds/:id", buildHandler.GetBuildStatus) protected.POST("/builds/:id/cancel", buildHandler.CancelBuild) protected.GET("/builds/:id/logs", buildHandler.GetBuildLogs) protected.POST("/builds/plan", buildHandler.GetBuildPlan) protected.GET("/builds/detect", buildHandler.DetectBuildType) // Scaling routes scalingHandler.RegisterRoutes(protected) haAPIManager.RegisterRoutes(protected) // Database routes protected.GET("/databases", databaseHandler.GetDatabases) protected.POST("/databases", databaseHandler.CreateDatabase) protected.GET("/databases/:id", databaseHandler.GetDatabase) protected.PUT("/databases/:id", databaseHandler.UpdateDatabase) protected.DELETE("/databases/:id", databaseHandler.DeleteDatabase) protected.POST("/databases/:id/action", databaseHandler.PerformDatabaseAction) protected.POST("/databases/:id/backup", databaseHandler.CreateBackup) protected.POST("/databases/:id/restore", databaseHandler.RestoreBackup) // Node Agent routes api := router.Group("/api") api.Use(middleware.Auth(cfg.JWTSecret)) agentHandler.SetupRoutes(api) // Preview Environments routes protected.GET("/projects/:id/preview-environments", handleGetPreviewEnvironments) protected.POST("/projects/:id/preview-environments", handleCreatePreviewEnvironment) protected.GET("/preview-environments/:id", handleGetPreviewEnvironment) protected.PUT("/preview-environments/:id", handleUpdatePreviewEnvironment) protected.DELETE("/preview-environments/:id", handleDeletePreviewEnvironment) protected.POST("/preview-environments/:id/promote", handlePromotePreviewEnvironment) protected.POST("/preview-environments/cleanup-expired", handleCleanupExpiredPreviewEnvironments) // Security routes protected.POST("/security/scans", securityHandler.StartSecurityScan) protected.GET("/security/scans/:id", securityHandler.GetSecurityScan) protected.GET("/projects/:id/security/history", securityHandler.GetProjectSecurityHistory) protected.GET("/projects/:id/vulnerabilities", securityHandler.GetVulnerabilities) protected.PUT("/vulnerabilities/:id", securityHandler.UpdateVulnerability) protected.POST("/security/compliance/assess", securityHandler.StartComplianceAssessment) protected.GET("/security/compliance/reports/:id", securityHandler.GetComplianceReport) protected.GET("/security/compliance/frameworks", securityHandler.GetComplianceFrameworks) protected.POST("/security/compliance/gdpr/init", securityHandler.InitializeGDPRFramework) protected.GET("/projects/:id/security/metrics", securityHandler.GetSecurityMetrics) protected.GET("/projects/:id/security/audit-logs", securityHandler.GetAuditLogs) // WebSocket endpoint protected.GET("/ws", handleWebSocket) // Templates routes protected.GET("/templates", handleGetTemplates) protected.GET("/templates/:id", handleGetTemplate) protected.POST("/templates/:id/deploy", handleCreateFromTemplate) // Cron Jobs routes protected.GET("/cron-jobs", handleGetCronJobs) protected.POST("/cron-jobs", handleCreateCronJob) protected.GET("/cron-jobs/:id", handleGetCronJob) protected.PUT("/cron-jobs/:id", handleUpdateCronJob) protected.DELETE("/cron-jobs/:id", handleDeleteCronJob) protected.GET("/cron-jobs/:id/executions", handleGetCronExecutions) protected.POST("/cron-jobs/:id/trigger", handleTriggerCronJob) // Audit Logs routes protected.GET("/audit-logs", handleGetAuditLogs) protected.GET("/audit-logs/:resource/:id", handleGetResourceAuditLogs) } // APwhy Gateway routes apwhy := router.Group("/api/v1") { // Health check (no auth required) apwhy.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{ "ok": true, "data": gin.H{ "status": "ok", "name": "Containr + APwhy", "database": "postgresql", "generatedAt": time.Now().UTC().Format(time.RFC3339), }, }) }) } // Protected APwhy routes (authentication required) protectedAPwhy := router.Group("/api/v1") protectedAPwhy.Use(middleware.Auth(cfg.JWTSecret)) { // Service management protectedAPwhy.GET("/services", handleAPwhyServicesList) protectedAPwhy.POST("/services", handleAPwhyServicesCreate) protectedAPwhy.PATCH("/services/:id", handleAPwhyServicesPatch) // API Keys protectedAPwhy.GET("/keys", handleAPwhyKeysList) protectedAPwhy.POST("/keys", handleAPwhyKeysCreate) protectedAPwhy.PATCH("/keys/:id", handleAPwhyKeysPatch) // Analytics protectedAPwhy.GET("/analytics/ops", handleAPwhyAnalyticsOps) protectedAPwhy.GET("/analytics/traffic", handleAPwhyAnalyticsTraffic) } } }