Add alternative names and cover art for soundtracks
Alternative names (#32): - PUT /api/v1/soundtracks/{uuid}/alternative-names replaces a soundtrack's alternative names (trimmed, deduplicated, without the soundtrack's own name). - alternative_names is in the soundtrack and statistics responses. - /find matches them too and shows which name matched. Covers (#12), stored in COVERS_PATH and off when it is not set: - GET, PUT (upload) and DELETE /api/v1/soundtracks/{uuid}/cover, and PUT .../cover/source to pick a SteamGridDB game or AniList anime by ID. Uploaded and picked covers are never replaced automatically. - With STEAMGRIDDB_API_KEY, a background job searches SteamGridDB, then AniList, by name, alternative names and the name without bracketed parts, at startup, after a sync and after names change. Search results are ranked by matching words, so "Civilization IV" gets "Sid Meier's Civilization IV". - cover_modified (Unix seconds) is in the soundtrack and statistics responses, so clients can cache covers. The new columns are added to migration 000006, which is not released yet. Closes #12, closes #32 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
f79f51ea4c
commit
bb8de5548d
39 files changed
+2418
-99
No files matched your search
@@ -74,6 +74,7 @@ func TestOpenAPIDocumentsTheV1API(t *testing.T) {
|
||||
}
|
||||
slices.Sort(operations)
|
||||
assert.Equal(t, []string{
|
||||
"DELETE /api/v1/soundtracks/{uuid}/cover",
|
||||
"DELETE /api/v1/token",
|
||||
"GET /api/v1/character",
|
||||
"GET /api/v1/character/details",
|
||||
@@ -102,6 +103,7 @@ func TestOpenAPIDocumentsTheV1API(t *testing.T) {
|
||||
"GET /api/v1/songs/{uuid}/file",
|
||||
"GET /api/v1/soundtracks",
|
||||
"GET /api/v1/soundtracks/{uuid}",
|
||||
"GET /api/v1/soundtracks/{uuid}/cover",
|
||||
"GET /api/v1/statistics/songs/least-played",
|
||||
"GET /api/v1/statistics/songs/most-played",
|
||||
"GET /api/v1/statistics/soundtracks",
|
||||
@@ -119,6 +121,9 @@ func TestOpenAPIDocumentsTheV1API(t *testing.T) {
|
||||
"POST /api/v1/token",
|
||||
"POST /api/v1/token/cleanup",
|
||||
"PUT /api/v1/music/played",
|
||||
"PUT /api/v1/soundtracks/{uuid}/alternative-names",
|
||||
"PUT /api/v1/soundtracks/{uuid}/cover",
|
||||
"PUT /api/v1/soundtracks/{uuid}/cover/source",
|
||||
}, operations, "/api/v1/sync/reset is hidden and legacy routes are not documented")
|
||||
|
||||
assert.Equal(t, "http", spec.Components.SecuritySchemes[bearerAuthName].Value.Type)
|
||||
@@ -169,7 +174,7 @@ func (v *specValidator) check(method, path, body string, wantStatus int, headers
|
||||
|
||||
func TestResponsesMatchOpenAPI(t *testing.T) {
|
||||
gitea := newGitea(t, http.StatusOK)
|
||||
e := newTestEnv(t, func(c *config.Config) { c.ReleasesURL = gitea })
|
||||
e := newTestEnv(t, func(c *config.Config) { c.ReleasesURL = gitea }, withCovers(t))
|
||||
v := newSpecValidator(t, e)
|
||||
song := e.addSong("Game A", "Song", "0123456789")
|
||||
songURL := "/api/v1/songs/" + song.Uuid.String()
|
||||
@@ -202,6 +207,20 @@ func TestResponsesMatchOpenAPI(t *testing.T) {
|
||||
// Library.
|
||||
v.check(http.MethodGet, "/api/v1/soundtracks", "", http.StatusOK)
|
||||
v.check(http.MethodGet, "/api/v1/soundtracks/"+song.SoundtrackUuid.String(), "", http.StatusOK)
|
||||
v.check(http.MethodPut, "/api/v1/soundtracks/"+song.SoundtrackUuid.String()+"/alternative-names",
|
||||
`{"names":["Game Alpha"]}`, http.StatusOK)
|
||||
v.check(http.MethodPut, "/api/v1/soundtracks/00000000-0000-0000-0000-000000000000/alternative-names",
|
||||
`{"names":[]}`, http.StatusNotFound)
|
||||
|
||||
// Covers.
|
||||
coverURL := "/api/v1/soundtracks/" + song.SoundtrackUuid.String() + "/cover"
|
||||
v.check(http.MethodGet, coverURL, "", http.StatusNotFound)
|
||||
v.check(http.MethodPut, coverURL, string(pngImage(t)), http.StatusNoContent, "Content-Type", "image/png")
|
||||
v.check(http.MethodPut, coverURL, "plain text", http.StatusBadRequest, "Content-Type", "image/png")
|
||||
v.check(http.MethodGet, coverURL, "", http.StatusOK)
|
||||
v.check(http.MethodPut, coverURL+"/source", `{"source":"anilist","id":"42"}`, http.StatusNoContent)
|
||||
v.check(http.MethodPut, coverURL+"/source", `{"source":"anilist","id":"7"}`, http.StatusNotFound)
|
||||
v.check(http.MethodDelete, coverURL, "", http.StatusNoContent)
|
||||
v.check(http.MethodGet, "/api/v1/soundtracks/00000000-0000-0000-0000-000000000000", "", http.StatusNotFound)
|
||||
v.check(http.MethodGet, songURL, "", http.StatusOK)
|
||||
v.check(http.MethodGet, songURL+"/file", "", http.StatusOK)
|
||||
|
||||
Reference in new issue
Block a user