138 lines
4.0 KiB
Go
138 lines
4.0 KiB
Go
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,
|
|
})
|
|
}
|
|
}
|