Complete implementation: - Go-based search service with PostgreSQL and Redis backend - BACKBEAT SDK integration for beat-aware search operations - Docker containerization with multi-stage builds - Comprehensive API endpoints for project analysis and search - Database migrations and schema management - GITEA integration for repository management - Team composition analysis and recommendations Key features: - Beat-synchronized search operations with timing coordination - Phase-based operation tracking (started → querying → ranking → completed) - Docker Swarm deployment configuration - Health checks and monitoring - Secure configuration with environment variables Architecture: - Microservice design with clean API boundaries - Background processing for long-running analysis - Modular internal structure with proper separation of concerns - Integration with CHORUS ecosystem via BACKBEAT timing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
207 lines
5.0 KiB
Go
207 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/chorus-services/whoosh/internal/config"
|
|
"github.com/chorus-services/whoosh/internal/database"
|
|
"github.com/chorus-services/whoosh/internal/server"
|
|
"github.com/kelseyhightower/envconfig"
|
|
"github.com/rs/zerolog"
|
|
"github.com/rs/zerolog/log"
|
|
)
|
|
|
|
const (
|
|
serviceName = "whoosh"
|
|
)
|
|
|
|
var (
|
|
// Build-time variables (set via ldflags)
|
|
version = "0.1.1-debug"
|
|
commitHash = "unknown"
|
|
buildDate = "unknown"
|
|
)
|
|
|
|
func main() {
|
|
// Parse command line flags
|
|
var (
|
|
healthCheck = flag.Bool("health-check", false, "Run health check and exit")
|
|
showVersion = flag.Bool("version", false, "Show version information and exit")
|
|
)
|
|
flag.Parse()
|
|
|
|
// Handle version flag
|
|
if *showVersion {
|
|
fmt.Printf("WHOOSH %s\n", version)
|
|
fmt.Printf("Commit: %s\n", commitHash)
|
|
fmt.Printf("Built: %s\n", buildDate)
|
|
return
|
|
}
|
|
|
|
// Handle health check flag
|
|
if *healthCheck {
|
|
if err := runHealthCheck(); err != nil {
|
|
log.Fatal().Err(err).Msg("Health check failed")
|
|
}
|
|
return
|
|
}
|
|
|
|
// Configure structured logging
|
|
setupLogging()
|
|
|
|
log.Info().
|
|
Str("service", serviceName).
|
|
Str("version", version).
|
|
Str("commit", commitHash).
|
|
Str("build_date", buildDate).
|
|
Msg("🎭 Starting WHOOSH - Autonomous AI Development Teams")
|
|
|
|
// Load configuration
|
|
var cfg config.Config
|
|
|
|
// Debug: Print all environment variables starting with WHOOSH
|
|
log.Debug().Msg("Environment variables:")
|
|
for _, env := range os.Environ() {
|
|
if strings.HasPrefix(env, "WHOOSH_") {
|
|
// Don't log passwords in full, just indicate they exist
|
|
if strings.Contains(env, "PASSWORD") {
|
|
parts := strings.SplitN(env, "=", 2)
|
|
if len(parts) == 2 && len(parts[1]) > 0 {
|
|
log.Debug().Str("env", parts[0]+"=[REDACTED]").Msg("Found password env var")
|
|
}
|
|
} else {
|
|
log.Debug().Str("env", env).Msg("Found env var")
|
|
}
|
|
}
|
|
}
|
|
|
|
if err := envconfig.Process("whoosh", &cfg); err != nil {
|
|
log.Fatal().Err(err).Msg("Failed to load configuration")
|
|
}
|
|
|
|
// Validate configuration
|
|
if err := cfg.Validate(); err != nil {
|
|
log.Fatal().Err(err).Msg("Invalid configuration")
|
|
}
|
|
|
|
log.Info().
|
|
Str("listen_addr", cfg.Server.ListenAddr).
|
|
Str("database_host", cfg.Database.Host).
|
|
Bool("redis_enabled", cfg.Redis.Enabled).
|
|
Msg("📋 Configuration loaded")
|
|
|
|
// Initialize database
|
|
db, err := database.NewPostgresDB(cfg.Database)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Failed to initialize database")
|
|
}
|
|
defer db.Close()
|
|
|
|
log.Info().Msg("🗄️ Database connection established")
|
|
|
|
// Run migrations
|
|
if cfg.Database.AutoMigrate {
|
|
log.Info().Msg("🔄 Running database migrations...")
|
|
if err := database.RunMigrations(cfg.Database.URL); err != nil {
|
|
log.Fatal().Err(err).Msg("Database migration failed")
|
|
}
|
|
log.Info().Msg("✅ Database migrations completed")
|
|
}
|
|
|
|
// Initialize server
|
|
srv, err := server.NewServer(&cfg, db)
|
|
if err != nil {
|
|
log.Fatal().Err(err).Msg("Failed to create server")
|
|
}
|
|
|
|
// Start server
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
go func() {
|
|
log.Info().
|
|
Str("addr", cfg.Server.ListenAddr).
|
|
Msg("🌐 Starting HTTP server")
|
|
|
|
if err := srv.Start(ctx); err != nil {
|
|
log.Error().Err(err).Msg("Server startup failed")
|
|
cancel()
|
|
}
|
|
}()
|
|
|
|
// Wait for shutdown signal
|
|
sigChan := make(chan os.Signal, 1)
|
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
select {
|
|
case sig := <-sigChan:
|
|
log.Info().Str("signal", sig.String()).Msg("🛑 Shutdown signal received")
|
|
case <-ctx.Done():
|
|
log.Info().Msg("🛑 Context cancelled")
|
|
}
|
|
|
|
// Graceful shutdown
|
|
log.Info().Msg("🔄 Starting graceful shutdown...")
|
|
|
|
shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer shutdownCancel()
|
|
|
|
if err := srv.Shutdown(shutdownCtx); err != nil {
|
|
log.Error().Err(err).Msg("Server shutdown failed")
|
|
}
|
|
|
|
log.Info().Msg("✅ WHOOSH shutdown complete")
|
|
}
|
|
|
|
func runHealthCheck() error {
|
|
// Simple health check - try to connect to health endpoint
|
|
client := &http.Client{Timeout: 5 * time.Second}
|
|
|
|
// Use localhost for health check
|
|
healthURL := "http://localhost:8080/health"
|
|
|
|
resp, err := client.Get(healthURL)
|
|
if err != nil {
|
|
return fmt.Errorf("health check request failed: %w", err)
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("health check returned status %d", resp.StatusCode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func setupLogging() {
|
|
// Configure zerolog for structured logging
|
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
|
|
|
// Set log level from environment
|
|
level := os.Getenv("LOG_LEVEL")
|
|
switch level {
|
|
case "debug":
|
|
zerolog.SetGlobalLevel(zerolog.DebugLevel)
|
|
case "info":
|
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
case "warn":
|
|
zerolog.SetGlobalLevel(zerolog.WarnLevel)
|
|
case "error":
|
|
zerolog.SetGlobalLevel(zerolog.ErrorLevel)
|
|
default:
|
|
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
}
|
|
|
|
// Pretty logging for development
|
|
if os.Getenv("ENVIRONMENT") == "development" {
|
|
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
|
}
|
|
} |