ML-185.1: expose named cover sizes in collection API
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
---
|
||||
id: ML-185.1
|
||||
title: Expose named cover sizes in collection API
|
||||
status: To Do
|
||||
assignee: []
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: "2026-05-15 21:20"
|
||||
updated_date: "2026-05-15 21:40"
|
||||
updated_date: "2026-05-15 21:44"
|
||||
labels:
|
||||
- api
|
||||
- artwork
|
||||
@@ -35,11 +36,11 @@ Context for implementer: `MusicLibraryWeb.CollectionJSON` currently builds `cove
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #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.
|
||||
- [ ] #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.
|
||||
- [ ] #5 Production Hurl smoke coverage captures and requests a cover URL from the new contract.
|
||||
- [x] #1 Collection API responses that render records include a named cover object with original, large, medium, and small URLs.
|
||||
- [x] #2 The generated URLs encode no width for original, 1000 px for large, 460 px for medium, and 80 px for small.
|
||||
- [x] #3 Legacy flat cover URL fields are removed from the collection JSON contract unless a compatibility decision is explicitly documented during implementation.
|
||||
- [x] #4 Controller tests assert the new cover contract for collection record responses.
|
||||
- [x] #5 Production Hurl smoke coverage captures and requests a cover URL from the new contract.
|
||||
<!-- AC:END -->
|
||||
|
||||
## 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.
|
||||
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 -->
|
||||
|
||||
## 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
|
||||
|
||||
@large_cover_width 1000
|
||||
@medium_cover_width 460
|
||||
@small_cover_width 80
|
||||
|
||||
def show(%{record: record}) do
|
||||
record(record)
|
||||
end
|
||||
@@ -34,12 +38,20 @@ defmodule MusicLibraryWeb.CollectionJSON do
|
||||
selected_release_id: record.selected_release_id,
|
||||
artists: Enum.map(record.artists, & &1.name),
|
||||
title: record.title,
|
||||
cover_url: url(~p"/api/v1/assets/#{Transform.new(hash: 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)}")
|
||||
covers: cover_urls(record.cover_hash)
|
||||
}
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
|
||||
import MusicLibrary.Fixtures.Records
|
||||
|
||||
alias MusicBrainz.Fixtures.Release, as: ReleaseFixtures
|
||||
alias MusicLibrary.Assets.Transform
|
||||
alias MusicLibrary.Secrets
|
||||
alias Req.Test
|
||||
|
||||
@@ -321,14 +322,18 @@ defmodule MusicLibraryWeb.CollectionControllerTest do
|
||||
"purchased_at" => record.purchased_at |> DateTime.to_iso8601(),
|
||||
"artists" => Enum.map(record.artists, & &1.name),
|
||||
"title" => record.title,
|
||||
"cover_url" =>
|
||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjpudWxsfQ",
|
||||
"thumb_url" =>
|
||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjo0ODB9",
|
||||
"mini_cover_url" =>
|
||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjoxNTB9",
|
||||
"micro_cover_url" =>
|
||||
"http://localhost:4002/api/v1/assets/eyJoYXNoIjoiNTk5NDA3RERGNjk5MDdENEE2MEZFMTNDQ0FBODI0RDI1Q0YwOERDMTI0RkQ2QUEzRThFN0VDRDk4Qzg4NUZGRSIsIndpZHRoIjo0MH0"
|
||||
"covers" => %{
|
||||
"original" => asset_url(record.cover_hash, nil),
|
||||
"large" => asset_url(record.cover_hash, 1000),
|
||||
"medium" => asset_url(record.cover_hash, 460),
|
||||
"small" => asset_url(record.cover_hash, 80)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ HTTP 200
|
||||
jsonpath "$.title" != null
|
||||
|
||||
[Captures]
|
||||
cover-url: jsonpath "$.cover_url"
|
||||
cover-url: jsonpath "$.covers.original"
|
||||
|
||||
# GET cover image
|
||||
GET {{cover-url}}
|
||||
|
||||
Reference in New Issue
Block a user