package handlers import ( "context" "net/http" "time" "gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/repository" "gitea.d3m0k1d.ru/d3m0k1d/HellreigN/backend/internal/storage" "github.com/gin-gonic/gin" ) type LogHandlers struct { LogRepo *repository.LogRepository } func NewLogHandlers(logRepo *repository.LogRepository) *LogHandlers { return &LogHandlers{LogRepo: logRepo} } type InsertLogRequest struct { Timestamp time.Time `json:"timestamp"` Level string `json:"level" binding:"required"` Service string `json:"service" binding:"required"` Agent string `json:"agent" binding:"required"` Message string `json:"message" binding:"required"` } // @Summary Insert log entry // @Description Inserts a single log entry into ClickHouse // @Tags logs // @Accept json // @Produce json // @Param body body InsertLogRequest true "Log entry" // @Success 201 {object} map[string]string // @Security Bearer // @Router /logs [post] func (lh *LogHandlers) Insert(c *gin.Context) { var req InsertLogRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if req.Timestamp.IsZero() { req.Timestamp = time.Now() } log := storage.LogEntry{ Timestamp: req.Timestamp, Level: req.Level, Service: req.Service, Agent: req.Agent, Message: req.Message, } if err := lh.LogRepo.Insert(c.Request.Context(), log); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to insert log"}) return } c.JSON(http.StatusCreated, gin.H{"status": "ok"}) } type InsertLogsRequest struct { Logs []InsertLogRequest `json:"logs" binding:"required"` } // @Summary Insert log entries (batch) // @Description Inserts multiple log entries into ClickHouse // @Tags logs // @Accept json // @Produce json // @Param body body InsertLogsRequest true "Log entries" // @Success 201 {object} map[string]string // @Security Bearer // @Router /logs/batch [post] func (lh *LogHandlers) InsertBatch(c *gin.Context) { var req InsertLogsRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } logs := make([]storage.LogEntry, len(req.Logs)) for i, l := range req.Logs { if l.Timestamp.IsZero() { l.Timestamp = time.Now() } logs[i] = storage.LogEntry{ Timestamp: l.Timestamp, Level: l.Level, Service: l.Service, Agent: l.Agent, Message: l.Message, } } if err := lh.LogRepo.InsertBatch(c.Request.Context(), logs); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to insert logs"}) return } c.JSON(http.StatusCreated, gin.H{"status": "ok", "count": len(logs)}) } type SearchLogsRequest struct { Level string `form:"level"` Service string `form:"service"` Agent string `form:"agent"` DateFrom string `form:"date_from"` DateTo string `form:"date_to"` Limit int `form:"limit"` Offset int `form:"offset"` } // @Summary Search logs // @Description Searches logs with various filters // @Tags logs // @Produce json // @Param level query string false "Log level (INFO, WARNING, ERROR, FATAL)" // @Param service query string false "Service name" // @Param agent query string false "Agent name" // @Param date_from query string false "Date from (RFC3339)" // @Param date_to query string false "Date to (RFC3339)" // @Param limit query int false "Limit results" default(100) // @Param offset query int false "Offset results" default(0) // @Success 200 {array} storage.LogEntry // @Security Bearer // @Router /logs [get] func (lh *LogHandlers) Search(c *gin.Context) { var req SearchLogsRequest if err := c.ShouldBindQuery(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } filter := repository.LogFilter{ Level: req.Level, Service: req.Service, Agent: req.Agent, Limit: req.Limit, Offset: req.Offset, } if req.DateFrom != "" { if t, err := time.Parse(time.RFC3339, req.DateFrom); err == nil { filter.DateFrom = t } } if req.DateTo != "" { if t, err := time.Parse(time.RFC3339, req.DateTo); err == nil { filter.DateTo = t } } if filter.Limit <= 0 { filter.Limit = 100 } logs, err := lh.LogRepo.Search(c.Request.Context(), filter) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to search logs"}) return } c.JSON(http.StatusOK, logs) } // @Summary Get distinct services // @Description Returns list of all unique service names in logs // @Tags logs // @Produce json // @Success 200 {array} string // @Security Bearer // @Router /logs/services [get] func (lh *LogHandlers) GetServices(c *gin.Context) { services, err := lh.LogRepo.GetDistinctServices(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get services"}) return } if services == nil { services = []string{} } c.JSON(http.StatusOK, services) } // @Summary Get distinct agents // @Description Returns list of all unique agent names in logs // @Tags logs // @Produce json // @Success 200 {array} string // @Security Bearer // @Router /logs/agents [get] func (lh *LogHandlers) GetAgents(c *gin.Context) { agents, err := lh.LogRepo.GetDistinctAgents(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get agents"}) return } if agents == nil { agents = []string{} } c.JSON(http.StatusOK, agents) } // @Summary Get distinct log levels // @Description Returns list of all unique log levels in logs // @Tags logs // @Produce json // @Success 200 {array} string // @Security Bearer // @Router /logs/levels [get] func (lh *LogHandlers) GetLevels(c *gin.Context) { levels, err := lh.LogRepo.GetDistinctLevels(c.Request.Context()) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get levels"}) return } if levels == nil { levels = []string{} } c.JSON(http.StatusOK, levels) } // Ensure context is used var _ = context.Background