ML-185.1: expose named cover sizes in collection API
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
---
|
---
|
||||||
id: ML-185.1
|
id: ML-185.1
|
||||||
title: Expose named cover sizes in collection API
|
title: Expose named cover sizes in collection API
|
||||||
status: To Do
|
status: Done
|
||||||
assignee: []
|
assignee:
|
||||||
|
- Codex
|
||||||
created_date: "2026-05-15 21:20"
|
created_date: "2026-05-15 21:20"
|
||||||
updated_date: "2026-05-15 21:40"
|
updated_date: "2026-05-15 21:44"
|
||||||
labels:
|
labels:
|
||||||
- api
|
- api
|
||||||
- artwork
|
- artwork
|
||||||
@@ -35,11 +36,11 @@ Context for implementer: `MusicLibraryWeb.CollectionJSON` currently builds `cove
|
|||||||
|
|
||||||
<!-- AC:BEGIN -->
|
<!-- AC:BEGIN -->
|
||||||
|
|
||||||
- [ ] #1 Collection API responses that render records include a named cover object with original, large, medium, and small URLs.
|
- [x] #1 Collection API responses that render records include a named cover object with original, large, medium, and small URLs.
|
||||||
- [ ] #2 The generated URLs encode no width for original, 1000 px for large, 460 px for medium, and 80 px for small.
|
- [x] #2 The generated URLs encode no width for original, 1000 px for large, 460 px for medium, and 80 px for small.
|
||||||
- [ ] #3 Legacy flat cover URL fields are removed from the collection JSON contract unless a compatibility decision is explicitly documented during implementation.
|
- [x] #3 Legacy flat cover URL fields are removed from the collection JSON contract unless a compatibility decision is explicitly documented during implementation.
|
||||||
- [ ] #4 Controller tests assert the new cover contract for collection record responses.
|
- [x] #4 Controller tests assert the new cover contract for collection record responses.
|
||||||
- [ ] #5 Production Hurl smoke coverage captures and requests a cover URL from the new contract.
|
- [x] #5 Production Hurl smoke coverage captures and requests a cover URL from the new contract.
|
||||||
<!-- AC:END -->
|
<!-- AC:END -->
|
||||||
|
|
||||||
## Implementation Plan
|
## Implementation Plan
|
||||||
@@ -52,3 +53,23 @@ Context for implementer: `MusicLibraryWeb.CollectionJSON` currently builds `cove
|
|||||||
4. Update `test/prod.hurl` to capture a cover URL from the new cover object.
|
4. Update `test/prod.hurl` to capture a cover URL from the new cover object.
|
||||||
5. Run `mix test test/music_library_web/controllers/collection_controller_test.exs` and any focused asset-controller test if URL generation or transform behavior changes.
|
5. Run `mix test test/music_library_web/controllers/collection_controller_test.exs` and any focused asset-controller test if URL generation or transform behavior changes.
|
||||||
<!-- SECTION:PLAN:END -->
|
<!-- SECTION:PLAN:END -->
|
||||||
|
|
||||||
|
## Implementation Notes
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:BEGIN -->
|
||||||
|
|
||||||
|
Implemented the collection record cover contract as a `covers` object with `original`, `large`, `medium`, and `small` URLs. The old flat fields were removed from `MusicLibraryWeb.CollectionJSON`; exact-map controller expectations verify the legacy fields are absent. Ran `mix test test/music_library_web/controllers/collection_controller_test.exs` successfully: 16 tests passed. Dependency warnings were emitted during compilation, but no warnings from the changed project files.
|
||||||
|
|
||||||
|
<!-- SECTION:NOTES:END -->
|
||||||
|
|
||||||
|
## Final Summary
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||||
|
|
||||||
|
Updated the collection API record representation to expose named cover variants through `covers.original`, `covers.large`, `covers.medium`, and `covers.small`. The generated asset transform widths are unscaled for `original`, 1000 px for `large`, 460 px for `medium`, and 80 px for `small`; the legacy flat cover URL fields are no longer emitted.
|
||||||
|
|
||||||
|
Updated the controller test expected payloads to assert the new exact response shape and switched the production Hurl smoke capture to `$.covers.original`.
|
||||||
|
|
||||||
|
Tests run: `mix test test/music_library_web/controllers/collection_controller_test.exs` (16 passed).
|
||||||
|
|
||||||
|
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ defmodule MusicLibraryWeb.CollectionJSON do
|
|||||||
|
|
||||||
alias MusicLibrary.Assets.Transform
|
alias MusicLibrary.Assets.Transform
|
||||||
|
|
||||||
|
@large_cover_width 1000
|
||||||
|
@medium_cover_width 460
|
||||||
|
@small_cover_width 80
|
||||||
|
|
||||||
def show(%{record: record}) do
|
def show(%{record: record}) do
|
||||||
record(record)
|
record(record)
|
||||||
end
|
end
|
||||||
@@ -34,12 +38,20 @@ defmodule MusicLibraryWeb.CollectionJSON do
|
|||||||
selected_release_id: record.selected_release_id,
|
selected_release_id: record.selected_release_id,
|
||||||
artists: Enum.map(record.artists, & &1.name),
|
artists: Enum.map(record.artists, & &1.name),
|
||||||
title: record.title,
|
title: record.title,
|
||||||
cover_url: url(~p"/api/v1/assets/#{Transform.new(hash: record.cover_hash)}"),
|
covers: cover_urls(record.cover_hash)
|
||||||
thumb_url: url(~p"/api/v1/assets/#{Transform.new(hash: record.cover_hash, width: 480)}"),
|
|
||||||
mini_cover_url:
|
|
||||||
url(~p"/api/v1/assets/#{Transform.new(hash: record.cover_hash, width: 150)}"),
|
|
||||||
micro_cover_url:
|
|
||||||
url(~p"/api/v1/assets/#{Transform.new(hash: record.cover_hash, width: 40)}")
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp cover_urls(cover_hash) do
|
||||||
|
%{
|
||||||
|
original: cover_url(cover_hash, nil),
|
||||||
|
large: cover_url(cover_hash, @large_cover_width),
|
||||||
|
medium: cover_url(cover_hash, @medium_cover_width),
|
||||||
|
small: cover_url(cover_hash, @small_cover_width)
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp cover_url(cover_hash, width) do
|
||||||
|
url(~p"/api/v1/assets/#{Transform.new(hash: cover_hash, width: width)}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
|
|||||||
import MusicLibrary.Fixtures.Records
|
import MusicLibrary.Fixtures.Records
|
||||||
|
|
||||||
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
|
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
|
||||||
|
alias MusicLibrary.Assets.Transform
|
||||||
alias MusicLibrary.Secrets
|
alias MusicLibrary.Secrets
|
||||||
alias Req.Test
|
alias Req.Test
|
||||||
|
|
||||||
@@ -321,14 +322,18 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
|
|||||||
"purchased_at" => record.purchased_at |> DateTime.to_iso8601(),
|
"purchased_at" => record.purchased_at |> DateTime.to_iso8601(),
|
||||||
"artists" => Enum.map(record.artists, & &1.name),
|
"artists" => Enum.map(record.artists, & &1.name),
|
||||||
"title" => record.title,
|
"title" => record.title,
|
||||||
"cover_url" =>
|
"covers" => %{
|
||||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjpudWxsfQ",
|
"original" => asset_url(record.cover_hash, nil),
|
||||||
"thumb_url" =>
|
"large" => asset_url(record.cover_hash, 1000),
|
||||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjo0ODB9",
|
"medium" => asset_url(record.cover_hash, 460),
|
||||||
"mini_cover_url" =>
|
"small" => asset_url(record.cover_hash, 80)
|
||||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjoxNTB9",
|
}
|
||||||
"micro_cover_url" =>
|
|
||||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjo0MH0"
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp asset_url(cover_hash, width) do
|
||||||
|
payload = Transform.new(hash: cover_hash, width: width)
|
||||||
|
|
||||||
|
"http://localhost:4002#{~p"/api/v1/assets/#{payload}"}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
+1
-1
@@ -21,7 +21,7 @@ HTTP 200
|
|||||||
jsonpath "$.title" != null
|
jsonpath "$.title" != null
|
||||||
|
|
||||||
[Captures]
|
[Captures]
|
||||||
cover-url: jsonpath "$.cover_url"
|
cover-url: jsonpath "$.covers.original"
|
||||||
|
|
||||||
# GET cover image
|
# GET cover image
|
||||||
GET {{cover-url}}
|
GET {{cover-url}}
|
||||||
|
|||||||
Reference in New Issue
Block a user