Merge pull request #1 from Mozart409/copilot/add-recent-downloads-endpoint
Add GET /api/media/recent_downloads JSON endpoint
This commit is contained in:
commit
d752afdee9
3 changed files with 172 additions and 0 deletions
62
lib/pinchflat_web/controllers/api/media_controller.ex
Normal file
62
lib/pinchflat_web/controllers/api/media_controller.ex
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
defmodule PinchflatWeb.Api.MediaController do
|
||||
use PinchflatWeb, :controller
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
|
||||
alias Pinchflat.Repo
|
||||
alias Pinchflat.Media.MediaItem
|
||||
|
||||
@default_limit 50
|
||||
@min_limit 1
|
||||
@max_limit 500
|
||||
|
||||
def recent_downloads(conn, params) do
|
||||
limit =
|
||||
params
|
||||
|> Map.get("limit", "#{@default_limit}")
|
||||
|> parse_int(@default_limit)
|
||||
|> clamp(@min_limit, @max_limit)
|
||||
|
||||
media_items =
|
||||
from(mi in MediaItem,
|
||||
where: not is_nil(mi.media_downloaded_at),
|
||||
order_by: [desc: mi.media_downloaded_at],
|
||||
limit: ^limit,
|
||||
select: %{
|
||||
id: mi.id,
|
||||
uuid: mi.uuid,
|
||||
title: mi.title,
|
||||
media_id: mi.media_id,
|
||||
source_id: mi.source_id,
|
||||
uploaded_at: mi.uploaded_at,
|
||||
media_downloaded_at: mi.media_downloaded_at,
|
||||
media_filepath: mi.media_filepath,
|
||||
thumbnail_filepath: mi.thumbnail_filepath,
|
||||
metadata_filepath: mi.metadata_filepath,
|
||||
nfo_filepath: mi.nfo_filepath,
|
||||
subtitle_filepaths: mi.subtitle_filepaths
|
||||
}
|
||||
)
|
||||
|> Repo.all()
|
||||
|
||||
conn
|
||||
|> put_status(:ok)
|
||||
|> json(%{data: media_items})
|
||||
end
|
||||
|
||||
defp parse_int(value, default) when is_binary(value) do
|
||||
case Integer.parse(value) do
|
||||
{int, _} -> int
|
||||
:error -> default
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_int(value, _default) when is_integer(value), do: value
|
||||
defp parse_int(_, default), do: default
|
||||
|
||||
defp clamp(value, min_val, max_val) do
|
||||
value
|
||||
|> Kernel.max(min_val)
|
||||
|> Kernel.min(max_val)
|
||||
end
|
||||
end
|
||||
|
|
@ -71,6 +71,13 @@ defmodule PinchflatWeb.Router do
|
|||
get "/healthcheck", HealthController, :check, log: false
|
||||
end
|
||||
|
||||
# No auth or CSRF protection for internal API endpoints
|
||||
scope "/api", PinchflatWeb do
|
||||
pipe_through :api
|
||||
|
||||
get "/media/recent_downloads", Api.MediaController, :recent_downloads
|
||||
end
|
||||
|
||||
scope "/dev" do
|
||||
pipe_through :browser
|
||||
|
||||
|
|
|
|||
103
test/pinchflat_web/controllers/api/media_controller_test.exs
Normal file
103
test/pinchflat_web/controllers/api/media_controller_test.exs
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
defmodule PinchflatWeb.Api.MediaControllerTest do
|
||||
use PinchflatWeb.ConnCase
|
||||
|
||||
import Pinchflat.MediaFixtures
|
||||
|
||||
describe "GET /api/media/recent_downloads" do
|
||||
test "returns only downloaded media items", %{conn: conn} do
|
||||
downloaded = media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
_not_downloaded = media_item_fixture(%{media_downloaded_at: nil})
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
ids = Enum.map(response["data"], & &1["id"])
|
||||
assert downloaded.id in ids
|
||||
assert length(response["data"]) == 1
|
||||
end
|
||||
|
||||
test "returns items ordered by media_downloaded_at descending", %{conn: conn} do
|
||||
older = media_item_fixture(%{media_downloaded_at: ~U[2024-01-01 00:00:00Z]})
|
||||
newer = media_item_fixture(%{media_downloaded_at: ~U[2024-06-01 00:00:00Z]})
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
ids = Enum.map(response["data"], & &1["id"])
|
||||
assert ids == [newer.id, older.id]
|
||||
end
|
||||
|
||||
test "default limit is 50", %{conn: conn} do
|
||||
for _ <- 1..55 do
|
||||
media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
end
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert length(response["data"]) == 50
|
||||
end
|
||||
|
||||
test "respects custom limit param", %{conn: conn} do
|
||||
for _ <- 1..10 do
|
||||
media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
end
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads?limit=5")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert length(response["data"]) == 5
|
||||
end
|
||||
|
||||
test "clamps limit to minimum of 1", %{conn: conn} do
|
||||
media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads?limit=0")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert length(response["data"]) == 1
|
||||
end
|
||||
|
||||
test "clamps limit to maximum of 500", %{conn: conn} do
|
||||
for _ <- 1..5 do
|
||||
media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
end
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads?limit=9999")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert length(response["data"]) == 5
|
||||
end
|
||||
|
||||
test "returns expected fields for each item", %{conn: conn} do
|
||||
media_item_fixture(%{media_downloaded_at: DateTime.utc_now()})
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
item = hd(response["data"])
|
||||
assert Map.has_key?(item, "id")
|
||||
assert Map.has_key?(item, "uuid")
|
||||
assert Map.has_key?(item, "title")
|
||||
assert Map.has_key?(item, "media_id")
|
||||
assert Map.has_key?(item, "source_id")
|
||||
assert Map.has_key?(item, "uploaded_at")
|
||||
assert Map.has_key?(item, "media_downloaded_at")
|
||||
assert Map.has_key?(item, "media_filepath")
|
||||
assert Map.has_key?(item, "thumbnail_filepath")
|
||||
assert Map.has_key?(item, "metadata_filepath")
|
||||
assert Map.has_key?(item, "nfo_filepath")
|
||||
assert Map.has_key?(item, "subtitle_filepaths")
|
||||
end
|
||||
|
||||
test "returns empty data when no downloaded items exist", %{conn: conn} do
|
||||
_not_downloaded = media_item_fixture(%{media_downloaded_at: nil})
|
||||
|
||||
conn = get(conn, "/api/media/recent_downloads")
|
||||
response = json_response(conn, 200)
|
||||
|
||||
assert response["data"] == []
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Reference in a new issue