From 8e4c318635ae1d2609c8bc451116c15f4af1086c Mon Sep 17 00:00:00 2001 From: Robert Andreas Kyllo Date: Mon, 28 Sep 2026 12:27:05 +0200 Subject: [PATCH 1/3] feat: #2 define Crate data model and GraphQL schema --- docs/data-model.md | 34 +++++++++++++ schema.graphql | 118 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 docs/data-model.md create mode 100644 schema.graphql diff --git a/docs/data-model.md b/docs/data-model.md new file mode 100644 index 0000000..875abab --- /dev/null +++ b/docs/data-model.md @@ -0,0 +1,34 @@ +# Crate data model + +This document describes the data the application will need. `schema.graphql` +is the GraphQL contract built from this model. Database tables, resolvers and +queries are deliberately left to M2. + +## Core catalog + +- An **album** has a title, an optional release year, one or more artists, zero + or more genres and an ordered list of tracks. +- An **artist** can belong to many albums, and an **album** can have many + artists. +- A **genre** can belong to many albums, and an **album** can have many genres. +- A **track** belongs to one album and has a title, track number and optional + duration. + +The two many-to-many relationships will use join tables in PostgreSQL in M2. + +## User-generated data + +- A **review** belongs to one album and one simulated user. A user can have one + review per album, so saving another review updates the existing one. +- A **crate item** belongs to one simulated user and one album. It represents + the user's personal wantlist. + +The user identity and persistence of reviews and crate items are M2 work. The +schema already describes the operations the client will need. + +## Search results + +`searchAlbums` accepts one search term, optional genre filters and a sort +choice. It returns a cursor-based connection so the client can load results in +pages. The connection also contains the total number of matches and genre +facet counts for the filter UI. diff --git a/schema.graphql b/schema.graphql new file mode 100644 index 0000000..f60f7fa --- /dev/null +++ b/schema.graphql @@ -0,0 +1,118 @@ +scalar DateTime + +type Query { + searchAlbums(input: AlbumSearchInput!, first: Int!, after: String): AlbumConnection! + album(id: ID!): Album + myCrate(first: Int!, after: String): CrateConnection! +} + +type Mutation { + upsertReview(input: UpsertReviewInput!): Review! + deleteReview(id: ID!): DeleteReviewPayload! + toggleCrate(albumId: ID!): ToggleCratePayload! +} + +type Album { + id: ID! + title: String! + releaseYear: Int + artists: [Artist!]! + tracks: [Track!]! + genres: [Genre!]! + averageRating: Float + reviewCount: Int! + reviews(first: Int!, after: String): ReviewConnection! + isInCrate: Boolean! +} + +type Artist { + id: ID! + name: String! +} + +type Track { + id: ID! + title: String! + trackNumber: Int! + durationSeconds: Int +} + +type Genre { + id: ID! + name: String! +} + +type Review { + id: ID! + rating: Int! + comment: String! + createdAt: DateTime! + updatedAt: DateTime! +} + +type CrateItem { + id: ID! + album: Album! + addedAt: DateTime! +} + +type AlbumConnection { + edges: [AlbumEdge!]! + pageInfo: PageInfo! + totalCount: Int! + genreFacets: [GenreFacet!]! +} + +type AlbumEdge { + cursor: String! + node: Album! +} + +type CrateConnection { + edges: [CrateEdge!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type CrateEdge { + cursor: String! + node: CrateItem! +} + +type PageInfo { + hasNextPage: Boolean! + endCursor: String +} + +type GenreFacet { + genre: Genre! + count: Int! +} + +type DeleteReviewPayload { + deletedReviewId: ID! +} + +type ToggleCratePayload { + item: CrateItem + isInCrate: Boolean! +} + +input AlbumSearchInput { + query: String! + genreIds: [ID!] + sort: AlbumSort! +} + +input UpsertReviewInput { + albumId: ID! + rating: Int! + comment: String! +} + +enum AlbumSort { + RELEVANCE + TITLE_ASC + RELEASE_YEAR_DESC + AVERAGE_RATING_DESC +} From b135e72a34454e0a7a4a13e0d9175905868cc9ff Mon Sep 17 00:00:00 2001 From: Robert Andreas Kyllo Date: Mon, 28 Sep 2026 19:16:58 +0200 Subject: [PATCH 2/3] fix: #2 address GraphQL schema review --- docs/data-model.md | 7 +++++-- schema.graphql | 23 ++++++++++++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/docs/data-model.md b/docs/data-model.md index 875abab..8aebd5b 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -19,9 +19,12 @@ The two many-to-many relationships will use join tables in PostgreSQL in M2. ## User-generated data - A **review** belongs to one album and one simulated user. A user can have one - review per album, so saving another review updates the existing one. + review per album, so saving another review updates the existing one. Album + details can also return the current user's review to prefill an edit form. - A **crate item** belongs to one simulated user and one album. It represents - the user's personal wantlist. + the user's personal wantlist. The schema has separate add and remove actions + so repeated requests cannot accidentally switch the album back to the wrong + state. The user identity and persistence of reviews and crate items are M2 work. The schema already describes the operations the client will need. diff --git a/schema.graphql b/schema.graphql index f60f7fa..5f4fd3b 100644 --- a/schema.graphql +++ b/schema.graphql @@ -7,9 +7,10 @@ type Query { } type Mutation { - upsertReview(input: UpsertReviewInput!): Review! + upsertReview(input: UpsertReviewInput!): UpsertReviewPayload! deleteReview(id: ID!): DeleteReviewPayload! - toggleCrate(albumId: ID!): ToggleCratePayload! + addToCrate(albumId: ID!): AddToCratePayload! + removeFromCrate(albumId: ID!): RemoveFromCratePayload! } type Album { @@ -22,6 +23,7 @@ type Album { averageRating: Float reviewCount: Int! reviews(first: Int!, after: String): ReviewConnection! + myReview: Review isInCrate: Boolean! } @@ -91,11 +93,22 @@ type GenreFacet { type DeleteReviewPayload { deletedReviewId: ID! + album: Album! } -type ToggleCratePayload { - item: CrateItem - isInCrate: Boolean! +type UpsertReviewPayload { + review: Review! + album: Album! +} + +type AddToCratePayload { + item: CrateItem! + album: Album! +} + +type RemoveFromCratePayload { + removedAlbumId: ID! + album: Album! } input AlbumSearchInput { From 3062a6ab1f4b949087b54e8665c2d5e6aa61b718 Mon Sep 17 00:00:00 2001 From: Robert Andreas Kyllo Date: Wed, 30 Sep 2026 13:34:56 +0200 Subject: [PATCH 3/3] fix: #2 add paginated review connection --- schema.graphql | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/schema.graphql b/schema.graphql index 5f4fd3b..5900c6b 100644 --- a/schema.graphql +++ b/schema.graphql @@ -70,6 +70,17 @@ type AlbumEdge { node: Album! } +type ReviewConnection { + edges: [ReviewEdge!]! + pageInfo: PageInfo! + totalCount: Int! +} + +type ReviewEdge { + cursor: String! + node: Review! +} + type CrateConnection { edges: [CrateEdge!]! pageInfo: PageInfo!