diff --git a/internal/services/image_processor/types.go b/internal/services/image_processor/types.go index 95fe862..572a1c8 100644 --- a/internal/services/image_processor/types.go +++ b/internal/services/image_processor/types.go @@ -11,6 +11,7 @@ const ( DataTypeStaff DataType = imagepath.TypeStaff DataTypeBanner DataType = imagepath.TypeBanner DataTypePoster DataType = imagepath.TypePoster + DataTypeWork DataType = imagepath.TypeWork ) type Payload struct { diff --git a/internal/services/imagepath/imagepath.go b/internal/services/imagepath/imagepath.go index a429b05..461bde6 100644 --- a/internal/services/imagepath/imagepath.go +++ b/internal/services/imagepath/imagepath.go @@ -16,6 +16,12 @@ const ( // fills a phone hero. Both are 2:3 "posters" in the everyday sense, so the // distinction is: root = whatever the scraper had, /posters/ = the good one. TypePoster = "Poster" + + // TypeWork is a source work's cover -- manga, light novel, novel. Kept under + // its own prefix rather than at the root beside anime because the root is + // keyed by anime id and a work id there would be indistinguishable from one, + // which is exactly the kind of collision this package exists to prevent. + TypeWork = "Work" ) // For builds the object path for an image record. @@ -47,6 +53,8 @@ func For(dataType, id, name string) (string, bool) { return "/banners/" + key, true case TypePoster: return "/posters/" + key, true + case TypeWork: + return "/works/" + key, true default: return "", false } diff --git a/internal/services/imagepath/imagepath_test.go b/internal/services/imagepath/imagepath_test.go index c62a85f..3f4b242 100644 --- a/internal/services/imagepath/imagepath_test.go +++ b/internal/services/imagepath/imagepath_test.go @@ -22,6 +22,7 @@ func TestFor(t *testing.T) { {"staff", TypeStaff, id, "", "/staff/" + id, true}, {"banner", TypeBanner, id, "", "/banners/" + id, true}, {"poster is its own path, not the root", TypePoster, id, "", "/posters/" + id, true}, + {"work", TypeWork, id, "", "/works/" + id, true}, // Name is only a fallback for messages published before producers sent // ids: in-flight during a rolling deploy, and the retry topic. @@ -64,4 +65,15 @@ func TestPosterDoesNotCollideWithAnimeRoot(t *testing.T) { if root == poster { t.Fatalf("anime and poster resolved to the same path %q", root) } + + // A work and an anime are different records that could share an id shape. + // If a work ever landed at the root it would overwrite an anime's poster, + // and nothing downstream would report it -- the upload succeeds either way. + work, ok := For(TypeWork, id, "") + if !ok { + t.Fatal("work path not ok") + } + if work == root { + t.Fatalf("work and anime resolved to the same path %q", work) + } }