From 2376daf91c63ceaabd99bcdea85cb616c83a21df Mon Sep 17 00:00:00 2001 From: GraceSolutions Date: Thu, 23 Apr 2026 12:14:50 -0400 Subject: [PATCH] feat(startup): wire runner, engine, and scheduler into foreground mode Derives a 32-byte AES key from the configured secret via SHA-256, constructs the ConnectionService, Runner, and Engine, and starts the Scheduler so enabled rules fire automatically. Injects the shared services into the HTTP server via a new Dependencies struct so API handlers can reuse them. --- backend/internal/cli/cli.go | 37 ++++++++++++++++++-- backend/internal/server/server.go | 56 ++++++++++++++++++++----------- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/backend/internal/cli/cli.go b/backend/internal/cli/cli.go index 909a0a3..100fe79 100644 --- a/backend/internal/cli/cli.go +++ b/backend/internal/cli/cli.go @@ -3,15 +3,22 @@ package cli import ( "context" + "crypto/sha256" "fmt" "os" "os/signal" "syscall" "github.com/Grace-Solutions/OrchestrAD/internal/config" + "github.com/Grace-Solutions/OrchestrAD/internal/crypto" "github.com/Grace-Solutions/OrchestrAD/internal/db" "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/repository" + "github.com/Grace-Solutions/OrchestrAD/internal/rules/engine" + "github.com/Grace-Solutions/OrchestrAD/internal/rules/runner" + "github.com/Grace-Solutions/OrchestrAD/internal/scheduler" "github.com/Grace-Solutions/OrchestrAD/internal/server" + "github.com/Grace-Solutions/OrchestrAD/internal/services" ) // RunInit initializes the application: validates config, initializes DB, runs migrations @@ -62,13 +69,39 @@ func RunForeground() error { return fmt.Errorf("running migrations: %w", err) } - // Create and start server - srv := server.New(cfg, database, logger) + // Derive a 32-byte AES key from the configured secret key + keyHash := sha256.Sum256(cfg.SecretKey) + encryptor, err := crypto.NewEncryptor(keyHash[:]) + if err != nil { + return fmt.Errorf("initializing encryptor: %w", err) + } + + // Build services, repositories, and the execution pipeline + connService := services.NewConnectionService(database.Conn(), encryptor, logger) + ruleRunner := runner.New(database.Conn(), connService, logger) + deps := server.Dependencies{ + Runner: ruleRunner, + Engine: engine.NewEngine(logger), + ConnService: connService, + RuleRepo: repository.NewRuleRepository(database.Conn()), + ConnRepo: repository.NewConnectionRepository(database.Conn()), + RunRepo: repository.NewRuleRunRepository(database.Conn()), + } // Setup graceful shutdown ctx, cancel := context.WithCancel(context.Background()) defer cancel() + // Start the scheduler so enabled rules fire automatically + sched := scheduler.New(database.Conn(), ruleRunner, logger) + if err := sched.Start(ctx); err != nil { + return fmt.Errorf("starting scheduler: %w", err) + } + defer sched.Stop() + + // Create HTTP server + srv := server.New(cfg, database, deps, logger) + sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) diff --git a/backend/internal/server/server.go b/backend/internal/server/server.go index 5f43e46..a2da339 100644 --- a/backend/internal/server/server.go +++ b/backend/internal/server/server.go @@ -10,27 +10,43 @@ import ( "github.com/Grace-Solutions/OrchestrAD/internal/config" "github.com/Grace-Solutions/OrchestrAD/internal/db" "github.com/Grace-Solutions/OrchestrAD/internal/logging" + "github.com/Grace-Solutions/OrchestrAD/internal/repository" + "github.com/Grace-Solutions/OrchestrAD/internal/rules/engine" + "github.com/Grace-Solutions/OrchestrAD/internal/rules/runner" + "github.com/Grace-Solutions/OrchestrAD/internal/services" "github.com/Grace-Solutions/OrchestrAD/internal/version" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" "github.com/go-chi/cors" ) +// Dependencies bundles the services the server needs to satisfy its routes. +type Dependencies struct { + Runner *runner.Runner + Engine *engine.Engine + ConnService *services.ConnectionService + RuleRepo *repository.RuleRepository + ConnRepo *repository.ConnectionRepository + RunRepo *repository.RuleRunRepository +} + // Server represents the HTTP server type Server struct { - config *config.Config - db *db.DB - logger *logging.Logger - router *chi.Mux - httpSrv *http.Server + config *config.Config + db *db.DB + logger *logging.Logger + deps Dependencies + router *chi.Mux + httpSrv *http.Server } // New creates a new Server instance -func New(cfg *config.Config, database *db.DB, logger *logging.Logger) *Server { +func New(cfg *config.Config, database *db.DB, deps Dependencies, logger *logging.Logger) *Server { s := &Server{ config: cfg, db: database, logger: logger, + deps: deps, router: chi.NewRouter(), } s.setupMiddleware() @@ -41,10 +57,10 @@ func New(cfg *config.Config, database *db.DB, logger *logging.Logger) *Server { func (s *Server) setupMiddleware() { // Request ID s.router.Use(middleware.RequestID) - + // Real IP from trusted proxies s.router.Use(middleware.RealIP) - + // Request logging s.router.Use(func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -54,10 +70,10 @@ func (s *Server) setupMiddleware() { s.logger.Info("HTTP", "%s %s %d %s", r.Method, r.URL.Path, ww.Status(), time.Since(start)) }) }) - + // Panic recovery s.router.Use(middleware.Recoverer) - + // CORS s.router.Use(cors.Handler(cors.Options{ AllowedOrigins: s.config.CORS.AllowedOrigins, @@ -73,13 +89,13 @@ func (s *Server) setupRoutes() { // Health check (unauthenticated) s.router.Get("/health", s.handleHealth) s.router.Get("/api/health", s.handleHealth) - + // API v1 routes s.router.Route("/api/v1", func(r chi.Router) { // System endpoints r.Get("/version", s.handleVersion) r.Get("/health", s.handleHealth) - + // Auth endpoints (to be implemented) r.Route("/auth", func(r chi.Router) { r.Post("/login", s.handleNotImplemented) @@ -87,7 +103,7 @@ func (s *Server) setupRoutes() { r.Get("/me", s.handleNotImplemented) r.Get("/csrf", s.handleNotImplemented) }) - + // Users (to be implemented) r.Route("/users", func(r chi.Router) { r.Get("/", s.handleNotImplemented) @@ -96,7 +112,7 @@ func (s *Server) setupRoutes() { r.Put("/{id}", s.handleNotImplemented) r.Delete("/{id}", s.handleNotImplemented) }) - + // Credentials r.Route("/credentials", func(r chi.Router) { r.Get("/", s.handleNotImplemented) @@ -106,7 +122,7 @@ func (s *Server) setupRoutes() { r.Delete("/{id}", s.handleNotImplemented) r.Post("/{id}/test", s.handleNotImplemented) }) - + // AD Connections r.Route("/ad-connections", func(r chi.Router) { r.Get("/", s.handleNotImplemented) @@ -118,7 +134,7 @@ func (s *Server) setupRoutes() { r.Post("/{id}/test", s.handleNotImplemented) r.Post("/{id}/query-preview", s.handleNotImplemented) }) - + // Schedules r.Route("/schedules", func(r chi.Router) { r.Get("/", s.handleNotImplemented) @@ -127,7 +143,7 @@ func (s *Server) setupRoutes() { r.Put("/{id}", s.handleNotImplemented) r.Delete("/{id}", s.handleNotImplemented) }) - + // Rules r.Route("/rules", func(r chi.Router) { r.Get("/", s.handleNotImplemented) @@ -140,14 +156,14 @@ func (s *Server) setupRoutes() { r.Post("/{id}/enable", s.handleNotImplemented) r.Post("/{id}/disable", s.handleNotImplemented) }) - + // Backups r.Route("/backups", func(r chi.Router) { r.Get("/", s.handleNotImplemented) r.Post("/", s.handleNotImplemented) r.Post("/{id}/restore", s.handleNotImplemented) }) - + // API Keys r.Route("/api-keys", func(r chi.Router) { r.Get("/", s.handleNotImplemented) @@ -167,7 +183,7 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { func (s *Server) handleVersion(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprintf(w, `{"version":"%s","build_time":"%s","git_commit":"%s"}`, + fmt.Fprintf(w, `{"version":"%s","build_time":"%s","git_commit":"%s"}`, version.Version, version.BuildTime, version.GitCommit) }