# Task 08: Server Wiring ## Objective Wire `cmd/server/main.go` — load config, connect to MongoDB, create all handlers, set up routes, start HTTP server with graceful shutdown. ## Implementation ```go func main() { // 1. Load config cfg, err := config.Load() // exit on error // 2. Set up structured logging slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slog.LevelInfo}))) // 3. Connect to MongoDB store, err := store.NewStore(ctx, cfg.MongoURI) defer store.Close(ctx) // 4. Create clients castClient := cast.NewClient(cfg.CastAPIURL, cfg.CastAPIKey, cfg.CastSenderID) ghlAPI := ghl.NewAPIClient() // 5. Create handlers oauthHandler := ghl.NewOAuthHandler(cfg.GHLClientID, cfg.GHLClientSecret, cfg.BaseURL, cfg.GHLConversationProviderID, store) webhookHandler, err := ghl.NewWebhookHandler(cfg.GHLWebhookPublicKey, castClient, ghlAPI, oauthHandler) // exit on error (PEM parsing failure) // 6. Set up router (chi) r := chi.NewRouter() // Middleware r.Use(middleware.RequestID) r.Use(middleware.RealIP) r.Use(middleware.Recoverer) r.Use(middleware.Timeout(60 * time.Second)) // Routes r.Get("/health", healthCheck) r.Get("/install", oauthHandler.HandleInstall) r.Get("/oauth-callback", oauthHandler.HandleCallback) r.Post("/api/ghl/v1/webhook/messages", webhookHandler.HandleWebhook) // 7. Start server with graceful shutdown srv := &http.Server{ Addr: ":" + cfg.Port, Handler: r, } // Signal handling go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) <-sigCh slog.Info("shutting down...") ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() srv.Shutdown(ctx) }() slog.Info("cast-ghl-provider started", "port", cfg.Port, "base_url", cfg.BaseURL) if err := srv.ListenAndServe(); err != http.ErrServerClosed { slog.Error("server error", "err", err) os.Exit(1) } } func healthCheck(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(200) w.Write([]byte(`{"status":"ok","service":"cast-ghl-provider"}`)) } ``` ## Middleware Use chi's built-in middleware: - `middleware.RequestID` — adds X-Request-Id - `middleware.RealIP` — trusts X-Forwarded-For / X-Real-IP - `middleware.Recoverer` — recovers panics, returns 500 - `middleware.Timeout` — 60s request timeout Do NOT add a custom logging middleware for now — slog in handlers is sufficient. ## Key behaviors - Config validation at startup — fail fast with clear error messages - MongoDB connection at startup — fail fast if can't connect - PEM key parsing at startup — fail fast if invalid - Graceful shutdown: wait up to 10s for in-flight requests - Health check always returns 200 + JSON (no auth required) - All slog output goes to stderr (not stdout) - Port defaults to 3002 ## Acceptance Criteria - [ ] `go build ./cmd/server/` produces working binary - [ ] Missing config → clear error message + exit - [ ] MongoDB connection failure → clear error message + exit - [ ] Invalid PEM key → clear error message + exit - [ ] `GET /health` returns `{"status":"ok"}` - [ ] `GET /install` redirects to GHL OAuth - [ ] `GET /oauth-callback` handles code exchange - [ ] `POST /api/ghl/v1/webhook/messages` processes webhooks - [ ] SIGINT/SIGTERM triggers graceful shutdown - [ ] Server logs startup info with port and base_url