Ctrl K
ComunidadGuías…

internal/handler/data_change_log_handler.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package handler

import (
	"encoding/json"
	"net/http"
	"strconv"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/johnzastrow/actalog/internal/domain"
	"github.com/johnzastrow/actalog/internal/service"
	"github.com/johnzastrow/actalog/pkg/logger"
	"github.com/johnzastrow/actalog/pkg/middleware"
)

// DataChangeLogHandler handles HTTP requests for data change logs
type DataChangeLogHandler struct {
	service *service.DataChangeLogService
	logger  *logger.Logger
}

// NewDataChangeLogHandler creates a new data change log handler
func NewDataChangeLogHandler(service *service.DataChangeLogService, logger *logger.Logger) *DataChangeLogHandler {
	return &DataChangeLogHandler{
		service: service,
		logger:  logger,
	}
}

// GetDataChangeLog handles GET /api/data-change-logs/:id
// @Summary      Get data change log (Admin)
// @Description  Retrieve a specific data change log entry by ID
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        id path int true "Data Change Log ID"
// @Success      200 {object} domain.DataChangeLog "Data change log entry"
// @Failure      400 {object} ErrorResponse "Invalid ID"
// @Failure      403 {object} ErrorResponse "Forbidden"
// @Failure      404 {object} ErrorResponse "Log not found"
// @Router       /data-change-logs/{id} [get]
func (h *DataChangeLogHandler) GetDataChangeLog(w http.ResponseWriter, r *http.Request) {
	// Only admins can access individual data change logs
	userRole, _ := middleware.GetUserRole(r.Context())
	if userRole != "admin" {
		if h.logger != nil {
			h.logger.Warn("Unauthorized access attempt to data change log")
		}
		respondError(w, http.StatusForbidden, "Forbidden")
		return
	}

	idStr := chi.URLParam(r, "id")
	id, err := strconv.ParseInt(idStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid ID")
		return
	}

	log, err := h.service.GetByID(id)
	if err != nil {
		if h.logger != nil {
			h.logger.Error("Failed to get data change log: %v", err)
		}
		respondError(w, http.StatusNotFound, "Data change log not found")
		return
	}

	respondJSON(w, http.StatusOK, log)
}

// ListDataChangeLogs handles GET /api/data-change-logs
// @Summary      List data change logs (Admin)
// @Description  Get a paginated list of data change logs with optional filters
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        entity_type query string false "Filter by entity type"
// @Param        entity_id query int false "Filter by entity ID"
// @Param        operation query string false "Filter by operation (create, update, delete)"
// @Param        user_id query int false "Filter by user ID"
// @Param        user_email query string false "Filter by user email"
// @Param        start_date query string false "Filter by start date (RFC3339 or YYYY-MM-DD)"
// @Param        end_date query string false "Filter by end date (RFC3339 or YYYY-MM-DD)"
// @Param        limit query int false "Max results (default 50)"
// @Param        offset query int false "Skip N results (default 0)"
// @Success      200 {object} map[string]interface{} "Data change logs with total count"
// @Failure      400 {object} ErrorResponse "Invalid parameters"
// @Failure      403 {object} ErrorResponse "Forbidden"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /data-change-logs [get]
func (h *DataChangeLogHandler) ListDataChangeLogs(w http.ResponseWriter, r *http.Request) {
	// Only admins can list all data change logs
	userRole, _ := middleware.GetUserRole(r.Context())
	if userRole != "admin" {
		if h.logger != nil {
			h.logger.Warn("Unauthorized access attempt to data change logs list")
		}
		respondError(w, http.StatusForbidden, "Forbidden")
		return
	}

	// Parse query parameters
	filters := domain.DataChangeLogFilters{}

	if entityType := r.URL.Query().Get("entity_type"); entityType != "" {
		filters.EntityType = &entityType
	}

	if entityIDStr := r.URL.Query().Get("entity_id"); entityIDStr != "" {
		entityID, err := strconv.ParseInt(entityIDStr, 10, 64)
		if err != nil {
			respondError(w, http.StatusBadRequest, "Invalid entity_id")
			return
		}
		filters.EntityID = &entityID
	}

	if operation := r.URL.Query().Get("operation"); operation != "" {
		filters.Operation = &operation
	}

	if userIDStr := r.URL.Query().Get("user_id"); userIDStr != "" {
		userID, err := strconv.ParseInt(userIDStr, 10, 64)
		if err != nil {
			respondError(w, http.StatusBadRequest, "Invalid user_id")
			return
		}
		filters.UserID = &userID
	}

	if userEmail := r.URL.Query().Get("user_email"); userEmail != "" {
		filters.UserEmail = &userEmail
	}

	if startDateStr := r.URL.Query().Get("start_date"); startDateStr != "" {
		startDate, err := time.Parse(time.RFC3339, startDateStr)
		if err != nil {
			// Try simpler date format
			startDate, err = time.Parse("2006-01-02", startDateStr)
			if err != nil {
				respondError(w, http.StatusBadRequest, "Invalid start_date format (use RFC3339 or YYYY-MM-DD)")
				return
			}
		}
		filters.StartDate = &startDate
	}

	if endDateStr := r.URL.Query().Get("end_date"); endDateStr != "" {
		endDate, err := time.Parse(time.RFC3339, endDateStr)
		if err != nil {
			// Try simpler date format
			endDate, err = time.Parse("2006-01-02", endDateStr)
			if err != nil {
				respondError(w, http.StatusBadRequest, "Invalid end_date format (use RFC3339 or YYYY-MM-DD)")
				return
			}
			// Set to end of day
			endDate = endDate.Add(24*time.Hour - time.Second)
		}
		filters.EndDate = &endDate
	}

	// Parse pagination
	limit := 50
	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		parsedLimit, err := strconv.Atoi(limitStr)
		if err == nil && parsedLimit > 0 {
			limit = parsedLimit
		}
	}

	offset := 0
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		parsedOffset, err := strconv.Atoi(offsetStr)
		if err == nil && parsedOffset >= 0 {
			offset = parsedOffset
		}
	}

	// Get logs and count
	logs, err := h.service.List(filters, limit, offset)
	if err != nil {
		if h.logger != nil {
			h.logger.Error("Failed to list data change logs: %v", err)
		}
		respondError(w, http.StatusInternalServerError, "Failed to retrieve data change logs")
		return
	}

	count, err := h.service.Count(filters)
	if err != nil {
		if h.logger != nil {
			h.logger.Error("Failed to count data change logs: %v", err)
		}
		// Continue anyway, just set count to 0
		count = 0
	}

	response := map[string]interface{}{
		"logs":   logs,
		"total":  count,
		"limit":  limit,
		"offset": offset,
	}

	respondJSON(w, http.StatusOK, response)
}

// GetEntityHistory handles GET /api/data-change-logs/entity/:entity_type/:entity_id
// @Summary      Get entity history (Admin)
// @Description  Get the change history for a specific entity
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        entity_type path string true "Entity type (e.g., user, workout, wod)"
// @Param        entity_id path int true "Entity ID"
// @Param        limit query int false "Max results (default 50)"
// @Param        offset query int false "Skip N results (default 0)"
// @Success      200 {object} map[string]interface{} "Entity change history"
// @Failure      400 {object} ErrorResponse "Invalid entity_id"
// @Failure      403 {object} ErrorResponse "Forbidden"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /data-change-logs/entity/{entity_type}/{entity_id} [get]
func (h *DataChangeLogHandler) GetEntityHistory(w http.ResponseWriter, r *http.Request) {
	// Only admins can view entity history
	userRole, _ := middleware.GetUserRole(r.Context())
	if userRole != "admin" {
		respondError(w, http.StatusForbidden, "Forbidden")
		return
	}

	entityType := chi.URLParam(r, "entity_type")
	entityIDStr := chi.URLParam(r, "entity_id")
	entityID, err := strconv.ParseInt(entityIDStr, 10, 64)
	if err != nil {
		respondError(w, http.StatusBadRequest, "Invalid entity_id")
		return
	}

	// Parse pagination
	limit := 50
	if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
		parsedLimit, err := strconv.Atoi(limitStr)
		if err == nil && parsedLimit > 0 {
			limit = parsedLimit
		}
	}

	offset := 0
	if offsetStr := r.URL.Query().Get("offset"); offsetStr != "" {
		parsedOffset, err := strconv.Atoi(offsetStr)
		if err == nil && parsedOffset >= 0 {
			offset = parsedOffset
		}
	}

	logs, err := h.service.GetByEntityID(entityType, entityID, limit, offset)
	if err != nil {
		if h.logger != nil {
			h.logger.Error("Failed to get entity history: %v", err)
		}
		respondError(w, http.StatusInternalServerError, "Failed to retrieve entity history")
		return
	}

	response := map[string]interface{}{
		"entity_type": entityType,
		"entity_id":   entityID,
		"logs":        logs,
	}

	respondJSON(w, http.StatusOK, response)
}

// CleanupOldLogs handles POST /api/admin/data-change-logs/cleanup
// @Summary      Cleanup old data change logs (Admin)
// @Description  Delete data change logs older than specified retention period
// @Tags         admin
// @Accept       json
// @Produce      json
// @Security     BearerAuth
// @Param        request body object true "Retention days (must be positive)"
// @Success      200 {object} map[string]interface{} "Deleted count and success message"
// @Failure      400 {object} ErrorResponse "Invalid request"
// @Failure      403 {object} ErrorResponse "Forbidden"
// @Failure      500 {object} ErrorResponse "Internal server error"
// @Router       /admin/data-change-logs/cleanup [post]
func (h *DataChangeLogHandler) CleanupOldLogs(w http.ResponseWriter, r *http.Request) {
	// Only admins can cleanup data change logs
	userRole, _ := middleware.GetUserRole(r.Context())
	if userRole != "admin" {
		respondError(w, http.StatusForbidden, "Forbidden")
		return
	}

	var request struct {
		RetentionDays int `json:"retention_days"`
	}

	if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
		respondError(w, http.StatusBadRequest, "Invalid request body")
		return
	}

	if request.RetentionDays <= 0 {
		respondError(w, http.StatusBadRequest, "retention_days must be positive")
		return
	}

	deletedCount, err := h.service.CleanupOldLogs(request.RetentionDays)
	if err != nil {
		if h.logger != nil {
			h.logger.Error("Failed to cleanup old data change logs: %v", err)
		}
		respondError(w, http.StatusInternalServerError, "Failed to cleanup data change logs")
		return
	}

	if h.logger != nil {
		adminUserID, _ := middleware.GetUserID(r.Context())
		h.logger.Info("Cleaned up old data change logs: retention_days=%d, deleted_count=%d, admin_user_id=%d",
			request.RetentionDays, deletedCount, adminUserID)
	}

	response := map[string]interface{}{
		"deleted_count": deletedCount,
		"message":       "Old data change logs deleted successfully",
	}

	respondJSON(w, http.StatusOK, response)
}