feat: edu-search-service migriert, voice-service/geo-service entfernt
All checks were successful
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-school (push) Successful in 28s
CI / test-go-edu-search (push) Successful in 27s
CI / test-python-klausur (push) Successful in 1m45s
CI / test-python-agent-core (push) Successful in 16s
CI / test-nodejs-website (push) Successful in 21s

- edu-search-service von breakpilot-pwa nach breakpilot-lehrer kopiert (ohne vendor)
- opensearch + edu-search-service in docker-compose.yml hinzugefuegt
- voice-service aus docker-compose.yml entfernt (jetzt in breakpilot-core)
- geo-service aus docker-compose.yml entfernt (nicht mehr benoetigt)
- CI/CD: edu-search-service zu Gitea Actions und Woodpecker hinzugefuegt
  (Go lint, test mit go mod download, build, SBOM)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Benjamin Boenisch
2026-02-15 18:36:38 +01:00
parent d4e1d6bab6
commit 414e0f5ec0
73 changed files with 23938 additions and 92 deletions

View File

@@ -0,0 +1,187 @@
package main
import (
"context"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/breakpilot/edu-search-service/internal/api/handlers"
"github.com/breakpilot/edu-search-service/internal/config"
"github.com/breakpilot/edu-search-service/internal/database"
"github.com/breakpilot/edu-search-service/internal/indexer"
"github.com/breakpilot/edu-search-service/internal/orchestrator"
"github.com/breakpilot/edu-search-service/internal/search"
"github.com/breakpilot/edu-search-service/internal/staff"
"github.com/gin-gonic/gin"
)
func main() {
log.Println("Starting edu-search-service...")
// Load configuration
cfg := config.Load()
log.Printf("Configuration loaded: Port=%s, OpenSearch=%s, Index=%s",
cfg.Port, cfg.OpenSearchURL, cfg.IndexName)
// Initialize OpenSearch indexer client
indexClient, err := indexer.NewClient(
cfg.OpenSearchURL,
cfg.OpenSearchUsername,
cfg.OpenSearchPassword,
cfg.IndexName,
)
if err != nil {
log.Fatalf("Failed to create indexer client: %v", err)
}
// Create index if not exists
ctx := context.Background()
if err := indexClient.CreateIndex(ctx); err != nil {
log.Printf("Warning: Could not create index (may already exist): %v", err)
}
// Initialize search service
searchService, err := search.NewService(
cfg.OpenSearchURL,
cfg.OpenSearchUsername,
cfg.OpenSearchPassword,
cfg.IndexName,
)
if err != nil {
log.Fatalf("Failed to create search service: %v", err)
}
// Initialize seed store for admin API
if err := handlers.InitSeedStore(cfg.SeedsDir); err != nil {
log.Printf("Warning: Could not initialize seed store: %v", err)
}
// Create handler
handler := handlers.NewHandler(cfg, searchService, indexClient)
// Initialize PostgreSQL for Staff/Publications database
dbCfg := &database.Config{
Host: cfg.DBHost,
Port: cfg.DBPort,
User: cfg.DBUser,
Password: cfg.DBPassword,
DBName: cfg.DBName,
SSLMode: cfg.DBSSLMode,
}
db, err := database.New(ctx, dbCfg)
if err != nil {
log.Printf("Warning: Could not connect to PostgreSQL for staff database: %v", err)
log.Println("Staff/Publications features will be disabled")
} else {
defer db.Close()
log.Println("Connected to PostgreSQL for staff/publications database")
// Run migrations
if err := db.RunMigrations(ctx); err != nil {
log.Printf("Warning: Could not run migrations: %v", err)
}
}
// Create repository for Staff handlers (may be nil if DB connection failed)
var repo *database.Repository
if db != nil {
repo = database.NewRepository(db)
}
// Setup Gin router
gin.SetMode(gin.ReleaseMode)
router := gin.New()
router.Use(gin.Recovery())
router.Use(gin.Logger())
// CORS middleware
router.Use(func(c *gin.Context) {
c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
c.Writer.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Next()
})
// Setup routes
handlers.SetupRoutes(router, handler, cfg.APIKey)
// Setup Staff/Publications routes if database is available
if repo != nil {
staffHandlers := handlers.NewStaffHandlers(repo, cfg.StaffCrawlerEmail)
apiV1 := router.Group("/api/v1")
staffHandlers.RegisterRoutes(apiV1)
log.Println("Staff/Publications API routes registered")
// Setup AI Extraction routes for vast.ai integration
aiHandlers := handlers.NewAIExtractionHandlers(repo)
aiHandlers.RegisterRoutes(apiV1)
log.Println("AI Extraction API routes registered")
}
// Setup Orchestrator routes if database is available
if db != nil {
orchRepo := orchestrator.NewPostgresRepository(db.Pool)
// Create real crawlers with adapters for orchestrator interface
staffCrawler := staff.NewStaffCrawler(repo)
staffAdapter := staff.NewOrchestratorAdapter(staffCrawler, repo)
pubAdapter := staff.NewPublicationOrchestratorAdapter(repo)
orch := orchestrator.NewOrchestrator(orchRepo, staffAdapter, pubAdapter)
orchHandler := handlers.NewOrchestratorHandler(orch, orchRepo)
v1 := router.Group("/v1")
v1.Use(handlers.AuthMiddleware(cfg.APIKey))
handlers.SetupOrchestratorRoutes(v1, orchHandler)
log.Println("Orchestrator API routes registered")
// Setup Audience routes (reuses orchRepo which implements AudienceRepository)
audienceHandler := handlers.NewAudienceHandler(orchRepo)
handlers.SetupAudienceRoutes(v1, audienceHandler)
log.Println("Audience API routes registered")
}
// Create HTTP server
srv := &http.Server{
Addr: ":" + cfg.Port,
Handler: router,
ReadTimeout: 10 * time.Second,
WriteTimeout: 30 * time.Second,
IdleTimeout: 60 * time.Second,
}
// Start server in goroutine
go func() {
log.Printf("Server listening on port %s", cfg.Port)
if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("Server error: %v", err)
}
}()
// Graceful shutdown
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(ctx); err != nil {
log.Fatalf("Server forced to shutdown: %v", err)
}
log.Println("Server exited")
}