Sync from /srv/compose/unified-media-manager
This commit is contained in:
137
internal/api/search.go
Normal file
137
internal/api/search.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/TopherMayor/unified-media-manager/internal/service"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func searchReleases(svc *service.SearchService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
query := c.QueryParam("query")
|
||||
if query == "" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "query parameter is required"})
|
||||
}
|
||||
|
||||
mediaType := c.QueryParam("media_type")
|
||||
|
||||
var indexerIDs []int64
|
||||
if ids := c.QueryParam("indexer_ids"); ids != "" {
|
||||
for _, idStr := range strings.Split(ids, ",") {
|
||||
idStr = strings.TrimSpace(idStr)
|
||||
if id, err := strconv.ParseInt(idStr, 10, 64); err == nil {
|
||||
indexerIDs = append(indexerIDs, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
req := service.SearchRequest{
|
||||
Query: query,
|
||||
MediaType: mediaType,
|
||||
IndexerIDs: indexerIDs,
|
||||
}
|
||||
|
||||
results, err := svc.Search(ctx, req)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]interface{}{
|
||||
"data": results,
|
||||
"total": len(results),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func grabRelease(svc *service.SearchService, dcSvc *service.DownloadClientService, queueSvc *service.QueueService, safetySvc *service.SafetyService, activitySvc *service.ActivityService) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var req service.GrabRequest
|
||||
if err := c.Bind(&req); err != nil {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request body"})
|
||||
}
|
||||
|
||||
if req.DownloadURL == "" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "download_url is required"})
|
||||
}
|
||||
if req.Title == "" {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "title is required"})
|
||||
}
|
||||
if req.MediaID == 0 {
|
||||
return c.JSON(http.StatusBadRequest, map[string]string{"error": "media_id is required"})
|
||||
}
|
||||
|
||||
// Safety check: block dangerous file extensions before download
|
||||
if safetySvc != nil {
|
||||
block := safetySvc.Check(req.Title, req.DownloadURL)
|
||||
if block != nil {
|
||||
if activitySvc != nil {
|
||||
activitySvc.LogAsync(service.LogEntry{
|
||||
EventType: "safety_block",
|
||||
MediaID: &req.MediaID,
|
||||
MediaType: &req.MediaType,
|
||||
Title: req.Title,
|
||||
Description: &block.Reason,
|
||||
Data: json.RawMessage(fmt.Sprintf(`{"extension":"%s","indexer":"%s"}`, block.MatchedExtension, req.IndexerName)),
|
||||
})
|
||||
}
|
||||
return c.JSON(http.StatusUnprocessableEntity, map[string]interface{}{
|
||||
"error": block.Reason,
|
||||
"blocked": true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result, err := svc.Grab(ctx, req, dcSvc)
|
||||
if err != nil {
|
||||
return c.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
|
||||
}
|
||||
|
||||
qualityJSON, _ := json.Marshal(req.Quality)
|
||||
_, err = queueSvc.CreateQueueEntry(ctx, service.CreateQueueEntryRequest{
|
||||
MediaID: req.MediaID,
|
||||
MediaType: req.MediaType,
|
||||
ReleaseTitle: req.Title,
|
||||
Indexer: req.IndexerName,
|
||||
DownloadClient: result.ClientName,
|
||||
Quality: qualityJSON,
|
||||
Protocol: result.Protocol,
|
||||
DownloadID: result.DownloadID,
|
||||
})
|
||||
if err != nil {
|
||||
slog.Error("failed to create queue entry", "error", err)
|
||||
}
|
||||
|
||||
// Log successful grab activity
|
||||
if activitySvc != nil {
|
||||
activitySvc.LogAsync(service.LogEntry{
|
||||
EventType: "grab",
|
||||
MediaID: &req.MediaID,
|
||||
MediaType: &req.MediaType,
|
||||
Title: fmt.Sprintf("Grabbed %s", req.Title),
|
||||
Description: &req.IndexerName,
|
||||
Data: json.RawMessage(fmt.Sprintf(`{"release":"%s","client":"%s","protocol":"%s"}`, req.Title, result.ClientName, result.Protocol)),
|
||||
})
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusCreated, map[string]interface{}{
|
||||
"queue_id": result.QueueID,
|
||||
"download_id": result.DownloadID,
|
||||
"client": result.ClientName,
|
||||
"protocol": result.Protocol,
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user