package handler import ( "bytes" "encoding/json" "net/http" "strconv" "github.com/johnzastrow/actalog/internal/domain" "github.com/johnzastrow/actalog/internal/service" "github.com/johnzastrow/actalog/pkg/logger" "github.com/johnzastrow/actalog/pkg/middleware" ) // BenchmarkHandler handles HTTP requests for benchmarks type BenchmarkHandler struct { service *service.BenchmarkService logger *logger.Logger } // NewBenchmarkHandler creates a new benchmark handler func NewBenchmarkHandler(service *service.BenchmarkService, logger *logger.Logger) *BenchmarkHandler { return &BenchmarkHandler{ service: service, logger: logger, } } // RunBenchmark handles POST /api/benchmark // @Summary Run benchmark suite // @Description Execute the full benchmark suite to test database, serialization, and business logic performance // @Tags benchmark // @Accept json // @Produce json // @Security BearerAuth // @Param concurrent query bool false "Include concurrent operation tests (default: false)" // @Param cleanup query bool false "Skip cleanup after run if set to false (default: true)" // @Param records query int false "Number of records to generate for benchmark testing (default: 1000, max: 500000)" // @Success 200 {object} map[string]interface{} "Benchmark results" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /benchmark [post] func (h *BenchmarkHandler) RunBenchmark(w http.ResponseWriter, r *http.Request) { userID, ok := middleware.GetUserID(r.Context()) if !ok { h.logger.Warn("Unauthorized benchmark attempt: no user context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // Parse record count from query parameters recordCount := domain.DefaultRecordCount if recordsStr := r.URL.Query().Get("records"); recordsStr != "" { if parsed, err := strconv.Atoi(recordsStr); err == nil && parsed > 0 { recordCount = parsed if recordCount > domain.MaxRecordCount { recordCount = domain.MaxRecordCount } } } // Parse options from query parameters options := domain.BenchmarkOptions{ IncludeConcurrent: r.URL.Query().Get("concurrent") == "true", Cleanup: r.URL.Query().Get("cleanup") != "false", // Default to true RecordCount: recordCount, } h.logger.Info("Running benchmark suite: user_id=%d concurrent=%v cleanup=%v records=%d", userID, options.IncludeConcurrent, options.Cleanup, options.RecordCount) result, err := h.service.RunBenchmark(userID, options) if err != nil { h.logger.Error("Benchmark failed: user_id=%d error=%v", userID, err) http.Error(w, "Benchmark failed: "+err.Error(), http.StatusInternalServerError) return } h.logger.Info("Benchmark completed: user_id=%d overall=%s duration_ms=%.2f ops=%d success=%d failed=%d records=%d", userID, result.Overall, result.TotalDurationMs, result.TotalOperations, result.SuccessfulOperations, result.FailedOperations, result.RecordCount) // Buffer the JSON response to set Content-Length header // This avoids chunked transfer encoding issues with long-running requests var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(result); err != nil { h.logger.Error("Failed to encode benchmark result: error=%v", err) http.Error(w, "Failed to encode result", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Length", strconv.Itoa(buf.Len())) w.Write(buf.Bytes()) } // GetBenchmarkStatus handles GET /api/benchmark/status // @Summary Get benchmark status // @Description Quick status check for the benchmark system // @Tags benchmark // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]interface{} "Benchmark status" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /benchmark/status [get] func (h *BenchmarkHandler) GetBenchmarkStatus(w http.ResponseWriter, r *http.Request) { _, ok := middleware.GetUserID(r.Context()) if !ok { h.logger.Warn("Unauthorized benchmark status request: no user context") http.Error(w, "Unauthorized", http.StatusUnauthorized) return } status, err := h.service.GetBenchmarkStatus() if err != nil { h.logger.Error("Failed to get benchmark status: error=%v", err) http.Error(w, "Failed to get status: "+err.Error(), http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) } // CleanupBenchmarkData handles DELETE /api/admin/benchmark/data // @Summary Cleanup all benchmark data (Admin) // @Description Delete all benchmark data from the database // @Tags benchmark // @Accept json // @Produce json // @Security BearerAuth // @Success 200 {object} map[string]string "Cleanup result" // @Failure 401 {object} ErrorResponse "Unauthorized" // @Failure 403 {object} ErrorResponse "Forbidden" // @Failure 500 {object} ErrorResponse "Internal server error" // @Router /admin/benchmark/data [delete] func (h *BenchmarkHandler) CleanupBenchmarkData(w http.ResponseWriter, r *http.Request) { userRole, _ := middleware.GetUserRole(r.Context()) if userRole != "admin" { h.logger.Warn("Unauthorized benchmark cleanup attempt: user_role=%s", userRole) http.Error(w, "Forbidden", http.StatusForbidden) return } err := h.service.CleanupAllBenchmarkData() if err != nil { h.logger.Error("Failed to cleanup benchmark data: error=%v", err) http.Error(w, "Failed to cleanup: "+err.Error(), http.StatusInternalServerError) return } h.logger.Info("Benchmark data cleaned up by admin") w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "success", "message": "All benchmark data has been deleted", }) }