mirror of
https://github.com/arkorty/DownLink.git
synced 2026-03-18 00:57:15 +00:00
feat: caching & logging
This commit is contained in:
73
backend/handlers/log.go
Normal file
73
backend/handlers/log.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"DownLink/services"
|
||||
)
|
||||
|
||||
// LogHandler handles requests for viewing logs
|
||||
type LogHandler struct {
|
||||
logBuffer *services.LogBuffer
|
||||
}
|
||||
|
||||
// NewLogHandler creates a new log handler with the provided log buffer
|
||||
func NewLogHandler(logBuffer *services.LogBuffer) *LogHandler {
|
||||
return &LogHandler{
|
||||
logBuffer: logBuffer,
|
||||
}
|
||||
}
|
||||
|
||||
// GetLogs returns the logs based on query parameters
|
||||
func (lh *LogHandler) GetLogs(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("Log retrieval requested", "remote_addr", r.RemoteAddr)
|
||||
|
||||
// Parse level parameter
|
||||
level := slog.LevelInfo
|
||||
if levelStr := r.URL.Query().Get("level"); levelStr != "" {
|
||||
switch levelStr {
|
||||
case "DEBUG", "debug":
|
||||
level = slog.LevelDebug
|
||||
case "INFO", "info":
|
||||
level = slog.LevelInfo
|
||||
case "WARN", "warn":
|
||||
level = slog.LevelWarn
|
||||
case "ERROR", "error":
|
||||
level = slog.LevelError
|
||||
}
|
||||
}
|
||||
|
||||
// Parse limit parameter
|
||||
limit := 0 // 0 means no limit
|
||||
if limitStr := r.URL.Query().Get("limit"); limitStr != "" {
|
||||
if parsedLimit, err := strconv.Atoi(limitStr); err == nil && parsedLimit > 0 {
|
||||
limit = parsedLimit
|
||||
}
|
||||
}
|
||||
|
||||
// Get logs filtered by level
|
||||
logs := lh.logBuffer.GetEntriesByLevel(level)
|
||||
|
||||
// Apply limit if specified
|
||||
if limit > 0 && limit < len(logs) {
|
||||
// Return the most recent logs (which are at the end of the array)
|
||||
startIdx := len(logs) - limit
|
||||
if startIdx < 0 {
|
||||
startIdx = 0
|
||||
}
|
||||
logs = logs[startIdx:]
|
||||
}
|
||||
|
||||
// Respond with logs
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
slog.Debug("Returning logs", "count", len(logs), "level", level.String())
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"logs": logs,
|
||||
"count": len(logs),
|
||||
})
|
||||
}
|
||||
109
backend/handlers/video.go
Normal file
109
backend/handlers/video.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"DownLink/models"
|
||||
"DownLink/services"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type VideoHandler struct {
|
||||
videoService *services.VideoService
|
||||
}
|
||||
|
||||
func NewVideoHandler(videoService *services.VideoService) *VideoHandler {
|
||||
return &VideoHandler{
|
||||
videoService: videoService,
|
||||
}
|
||||
}
|
||||
|
||||
func (vh *VideoHandler) DownloadVideo(w http.ResponseWriter, r *http.Request) {
|
||||
var req models.VideoDownloadRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
slog.Error("Failed to decode request body", "error", err)
|
||||
vh.writeError(w, http.StatusBadRequest, "Invalid JSON")
|
||||
return
|
||||
}
|
||||
|
||||
if req.URL == "" || req.Quality == "" {
|
||||
slog.Warn("Invalid request parameters", "url", req.URL, "quality", req.Quality)
|
||||
vh.writeError(w, http.StatusBadRequest, "URL and Quality are required")
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("Starting video download", "url", req.URL, "quality", req.Quality)
|
||||
|
||||
outputPath, err := vh.videoService.DownloadVideo(req.URL, req.Quality)
|
||||
if err != nil {
|
||||
slog.Error("Video download failed", "url", req.URL, "quality", req.Quality, "error", err)
|
||||
vh.writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// Determine if this was a cached response
|
||||
isCached := !strings.Contains(outputPath, "dl_") && strings.Contains(outputPath, "cache")
|
||||
|
||||
// Only cleanup if it's a fresh download (not cached)
|
||||
if strings.Contains(outputPath, "dl_") {
|
||||
defer vh.videoService.CleanupTempDir(outputPath)
|
||||
}
|
||||
|
||||
uid := uuid.New().String()
|
||||
filename := fmt.Sprintf("video_%s.mp4", uid)
|
||||
|
||||
// Add cache status header
|
||||
if isCached {
|
||||
w.Header().Set("X-Cache-Status", "HIT")
|
||||
slog.Info("Serving cached video", "url", req.URL, "quality", req.Quality, "file", outputPath)
|
||||
} else {
|
||||
w.Header().Set("X-Cache-Status", "MISS")
|
||||
slog.Info("Serving fresh download", "url", req.URL, "quality", req.Quality, "file", outputPath)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
||||
w.Header().Set("Content-Type", "video/mp4")
|
||||
|
||||
http.ServeFile(w, r, outputPath)
|
||||
}
|
||||
|
||||
func (vh *VideoHandler) HealthCheck(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("Health check requested", "remote_addr", r.RemoteAddr)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte("Backend for DownLink is running.\n"))
|
||||
}
|
||||
|
||||
func (vh *VideoHandler) ClearCache(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Info("Cache clear requested")
|
||||
if err := vh.videoService.CleanupExpiredCache(0); err != nil {
|
||||
slog.Error("Failed to clear cache", "error", err)
|
||||
vh.writeError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to clear cache: %v", err))
|
||||
return
|
||||
}
|
||||
|
||||
slog.Info("Cache cleared successfully")
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "Cache cleared successfully"})
|
||||
}
|
||||
|
||||
func (vh *VideoHandler) GetCacheStatus(w http.ResponseWriter, r *http.Request) {
|
||||
slog.Debug("Cache status requested")
|
||||
status := vh.videoService.GetCacheStats()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(status)
|
||||
}
|
||||
|
||||
func (vh *VideoHandler) writeError(w http.ResponseWriter, status int, message string) {
|
||||
slog.Error("HTTP error response", "status", status, "message", message)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(models.ErrorResponse{Error: message})
|
||||
}
|
||||
Reference in New Issue
Block a user