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 Dev 19855efacc
Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
feat: BreakPilot PWA - Full codebase (clean push without large binaries)
All services: admin-v2, studio-v2, website, ai-compliance-sdk,
consent-service, klausur-service, voice-service, and infrastructure.
Large PDFs and compiled binaries excluded via .gitignore.
2026-02-11 13:25:58 +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)
}
}