This repository has been archived on 2026-02-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
breakpilot-pwa/edu-search-service/internal/api/handlers/handlers.go
Benjamin Admin bfdaf63ba9 fix: Restore all files lost during destructive rebase
A previous `git pull --rebase origin main` dropped 177 local commits,
losing 3400+ files across admin-v2, backend, studio-v2, website,
klausur-service, and many other services. The partial restore attempt
(660295e2) only recovered some files.

This commit restores all missing files from pre-rebase ref 98933f5e
while preserving post-rebase additions (night-scheduler, night-mode UI,
NightModeWidget dashboard integration).

Restored features include:
- AI Module Sidebar (FAB), OCR Labeling, OCR Compare
- GPU Dashboard, RAG Pipeline, Magic Help
- Klausur-Korrektur (8 files), Abitur-Archiv (5+ files)
- Companion, Zeugnisse-Crawler, Screen Flow
- Full backend, studio-v2, website, klausur-service
- All compliance SDKs, agent-core, voice-service
- CI/CD configs, documentation, scripts

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 09:51:32 +01:00

147 lines
3.5 KiB
Go

package handlers
import (
"net/http"
"github.com/breakpilot/edu-search-service/internal/config"
"github.com/breakpilot/edu-search-service/internal/indexer"
"github.com/breakpilot/edu-search-service/internal/search"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
)
// Handler contains all HTTP handlers
type Handler struct {
cfg *config.Config
searchService *search.Service
indexClient *indexer.Client
}
// NewHandler creates a new handler instance
func NewHandler(cfg *config.Config, searchService *search.Service, indexClient *indexer.Client) *Handler {
return &Handler{
cfg: cfg,
searchService: searchService,
indexClient: indexClient,
}
}
// Health returns service health status
func (h *Handler) Health(c *gin.Context) {
status := "ok"
// Check OpenSearch health
osStatus, err := h.indexClient.Health(c.Request.Context())
if err != nil {
status = "degraded"
osStatus = "unreachable"
}
c.JSON(http.StatusOK, gin.H{
"status": status,
"opensearch": osStatus,
"service": "edu-search-service",
"version": "0.1.0",
})
}
// Search handles /v1/search requests
func (h *Handler) Search(c *gin.Context) {
var req search.SearchRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body", "details": err.Error()})
return
}
// Set defaults
if req.Limit <= 0 || req.Limit > 100 {
req.Limit = 10
}
if req.Mode == "" {
req.Mode = "keyword" // MVP: only BM25
}
// Generate query ID
queryID := uuid.New().String()
// Execute search
result, err := h.searchService.Search(c.Request.Context(), &req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "Search failed", "details": err.Error()})
return
}
result.QueryID = queryID
c.JSON(http.StatusOK, result)
}
// GetDocument retrieves a single document
func (h *Handler) GetDocument(c *gin.Context) {
docID := c.Query("doc_id")
if docID == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "doc_id parameter required"})
return
}
// TODO: Implement document retrieval
c.JSON(http.StatusNotImplemented, gin.H{"error": "Not implemented yet"})
}
// AuthMiddleware validates API keys
func AuthMiddleware(apiKey string) gin.HandlerFunc {
return func(c *gin.Context) {
// Skip auth for health endpoint
if c.Request.URL.Path == "/v1/health" {
c.Next()
return
}
// Check API key
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Missing Authorization header"})
return
}
// Extract Bearer token
if len(authHeader) < 7 || authHeader[:7] != "Bearer " {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid Authorization format"})
return
}
token := authHeader[7:]
if apiKey != "" && token != apiKey {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid API key"})
return
}
c.Next()
}
}
// RateLimitMiddleware implements basic rate limiting
func RateLimitMiddleware() gin.HandlerFunc {
// TODO: Implement proper rate limiting with Redis
return func(c *gin.Context) {
c.Next()
}
}
// SetupRoutes configures all API routes
func SetupRoutes(r *gin.Engine, h *Handler, apiKey string) {
// Health endpoint (no auth)
r.GET("/v1/health", h.Health)
// API v1 group with auth
v1 := r.Group("/v1")
v1.Use(AuthMiddleware(apiKey))
v1.Use(RateLimitMiddleware())
{
v1.POST("/search", h.Search)
v1.GET("/document", h.GetDocument)
// Admin routes
SetupAdminRoutes(v1, h)
}
}