diff --git a/AGENTS.md b/AGENTS.md index 6e93ec6..b933680 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -10,8 +10,7 @@ personal wantlist ("crate"). IT2810 (NTNU) Project 2. Group T19: `erikhfj`, `robertky`, `evenkkl`, `rachelks`. -**Status: M1 - spec and mockup.** `client/` renders mock data. `server/` -(PostgreSQL + GraphQL) arrives in M2, `e2e/` (Playwright) in M4. +**Status: M2 – PostgreSQL and GraphQL.** The frontend supports album and artist search, while the backend implements searching, filtering, sorting and pagination. Album details, reviews, crate functionality, simulated user identity and additional frontend controls are planned for later milestones. See the README for current implementation status. ## Commands @@ -26,7 +25,7 @@ IT2810 (NTNU) Project 2. Group T19: `erikhfj`, `robertky`, `evenkkl`, `rachelks` | `pnpm test` | Vitest, single run | Run `pnpm format:check && pnpm lint && pnpm typecheck && pnpm test` before -opening a PR. CI enforces the same gates. +opening a PR. CI enforces the same gates, plus build and the client bundle budget. ## Architecture rules @@ -77,12 +76,13 @@ requirements work - do not casually change them. ## Layout ``` -client/ React + Vite + TypeScript (mock data at M1) -docs/ workflow, AI usage log (from #4) +client/ React + Vite + TypeScript + Apollo Client +server/ GraphQL Yoga + Drizzle + PostgreSQL, migrations, seeds, SQL checks +docs/ architecture, data model, UI specification, workflow, AI log, VM runbook ``` -`server/` (GraphQL Yoga + Drizzle + PostgreSQL) and `e2e/` (Playwright) join -as they land. +`schema.graphql` is the shared API contract; `e2e/` (Playwright) is planned. +Local setup and environment variables are documented in `README.md`. ## Infrastructure @@ -95,6 +95,4 @@ The finished app runs on the group VM: client under - Apache `ProxyPass /graphql` to `127.0.0.1:3001`, client at `/var/www/html/project2` - CI runs on a self-hosted runner on the same machine and deploys on push to `main` -Until M2 lands `server/`, the VM's `~/T19-Project-2` is a manual copy of an -early prototype rather than a git clone, so `crate-api` is running code that -is not in this repository. +CI deploys the tested commit to the VM, applies database migrations and restarts the API. See `docs/VM-setup.md` for deployment instructions and environment configuration. diff --git a/README.md b/README.md index 7b17ea2..b92c30c 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,16 @@ Repository for team IT2810-H26-T19 in IT2810-H26. -**Crate** - a searchable catalog of albums. Search by word, filter and sort the -results, open an album for detail, and persist ratings, reviews and a personal -wantlist ("crate"). +**Crate** is an album catalog where users can search for music. The GraphQL API +already supports filtering, sorting and cursor-based pagination, while the +frontend controls for these features are planned for later milestones. Album +details, ratings, reviews and a personal wantlist ("crate") are also planned. -IT2810 (NTNU) Project 2. Group T19: `erikhfj`, `robertky`, `evenkkl`, -`rachelks`. +IT2810 (NTNU) Project 2. Group T19: `erikhfj`, `robertky`, `evenkkl`, `rachelks`. | | | | ----------------- | --------------------------------------------------------- | -| Current milestone | **M2 - PostgreSQL and GraphQL on the VM**, due 16 October | +| Current milestone | **M2 – PostgreSQL and GraphQL on the VM**, due 16 October | | Live prototype | http://it2810-19.idi.ntnu.no/project2 | | Workflow | [`docs/WORKFLOW.md`](docs/WORKFLOW.md) | | AI usage log | [`docs/AI-USAGE.md`](docs/AI-USAGE.md) | @@ -19,99 +19,104 @@ IT2810 (NTNU) Project 2. Group T19: `erikhfj`, `robertky`, `evenkkl`, ## Getting started -Requires Node.js 24+ and [pnpm](https://pnpm.io) 10+. +Requires Node.js 24+, pnpm 10.33.0 and PostgreSQL. Run commands from the repository root unless stated otherwise. ```bash pnpm install ``` -To also run the API locally you need PostgreSQL and a `DATABASE_URL` in -`server/.env`: +Create a local PostgreSQL database and user. Connect as a PostgreSQL administrator (for example, `psql -U postgres`) and run: -```bash -pnpm --filter @t19/server db:migrate # create the catalog tables -pnpm --filter @t19/server db:seed # optional: replace the catalog with the sample albums -pnpm --filter @t19/server dev # GraphQL Yoga on :3001/graphql +```sql +CREATE ROLE t19 WITH LOGIN PASSWORD 'choose-a-local-password'; +CREATE DATABASE crate OWNER t19; ``` -Keep the API running in one terminal, then start the frontend in another: +Create `server/.env` with the matching connection details: + +```dotenv +DATABASE_URL=postgres://t19:choose-a-local-password@127.0.0.1:5432/crate +``` + +Apply the database migrations and start the backend: ```bash -pnpm dev # Vite dev server on :5173, client only +pnpm --filter @t19/server db:migrate +pnpm --filter @t19/server db:seed # optional: reset to sample albums +pnpm --filter @t19/server dev ``` -Open to use the application. +Keep the backend running and start the frontend in a second terminal: -The frontend communicates with the GraphQL backend through Apollo Client. During local development, Vite forwards API requests to the backend running on port 3001. On the VM, Apache handles this routing instead. +```bash +pnpm dev +``` + +Open http://localhost:5173/project2/. The backend runs at http://localhost:3001/graphql. Vite forwards frontend GraphQL requests to the backend during development; Apache handles this on the VM. -To use a different GraphQL endpoint, set `VITE_GRAPHQL_URL` in `client/.env.local`. +The default frontend GraphQL endpoint is `/graphql`. To use a different endpoint, set `VITE_GRAPHQL_URL` in `client/.env.local` and restart Vite. -`db:seed` is a reset command, not a loader. It replaces the catalog and, -through foreign-key cascades, deletes the reviews and crate items hanging -off it. Do not run it against a database holding user content you want. +**Note:** `db:seed` replaces the catalog and deletes associated tracks, reviews and crate items. Do not use it on a database containing data you want to keep. See [VM setup](docs/VM-setup.md) for deployment instructions. -`docs/VM-setup.md` has the same steps for the group VM, plus a smoke suite -to check a deployment. +### Checks and useful commands -Before opening a PR: +Before opening a PR, run: ```bash pnpm format:check && pnpm lint && pnpm typecheck && pnpm test ``` -All four must pass. CI runs the same thing. - -| Command | Purpose | -| -------------------------------------- | ---------------------------------------------- | -| `pnpm dev` | Vite dev server, client only | -| `pnpm build` | Type-check + production build, every workspace | -| `pnpm lint` | ESLint - must pass with 0 warnings | -| `pnpm format` / `pnpm format:check` | Prettier | -| `pnpm typecheck` | `tsc --noEmit` in every workspace | -| `pnpm test` | Vitest, single run | -| `pnpm --filter @t19/server dev` | GraphQL API on `:3001/graphql` | -| `pnpm --filter @t19/server db:migrate` | Apply the Drizzle migrations | -| `pnpm --filter @t19/server db:seed` | Load the sample catalog | -| `pnpm db:seed:large` | Reset to a synthetic large catalog | +CI also builds the project and checks the client bundle size. + +| Command | Purpose | +| -------------------------------------- | ---------------------------------- | +| `pnpm dev` | Start the frontend | +| `pnpm build` | Build the project | +| `pnpm lint` | Run ESLint | +| `pnpm format` / `pnpm format:check` | Format or check formatting | +| `pnpm typecheck` | Run TypeScript checks | +| `pnpm test` | Run unit tests | +| `pnpm --filter @t19/server dev` | Start the GraphQL API | +| `pnpm --filter @t19/server db:migrate` | Apply database migrations | +| `pnpm --filter @t19/server db:seed` | Reset to the sample catalog | +| `pnpm db:seed:large` | Reset to a large synthetic catalog | + +Unit tests cover frontend components and backend logic, but do not test a running PostgreSQL database. The [VM setup guide](docs/VM-setup.md) includes API smoke tests. ## Database seed data -`db:seed` replaces the catalog with 16 curated albums for normal development. -For scalability testing, `db:seed:large` generates 100,000 deterministic -synthetic albums in batches of 1,000. Set `ALBUM_COUNT` to change the size: +`db:seed` loads 16 curated albums for development. For scalability testing, `db:seed:large` generates 100,000 deterministic synthetic albums in batches of 1,000. You can change the size with `ALBUM_COUNT`: ```bash ALBUM_COUNT=100000 pnpm db:seed:large ``` -PowerShell: +In PowerShell: ```powershell $env:ALBUM_COUNT=100000; pnpm db:seed:large ``` -Both seed commands reset the catalog and delete associated reviews and crate -items. They are development/reset tools, never commands to run against user -data you intend to keep. Each batch commits separately; if large seeding is -interrupted, rerun the command to reset and regenerate the catalog. +**Both commands reset the catalog and remove associated tracks, reviews and crate items.** If a large seed is interrupted, rerun it to reset and regenerate the catalog. ### Scalability check -The large seed is deterministic, so the same commands can repeat a performance -check. Run the committed [SQL checks](server/scripts/perf-check.sql), then send -the committed [GraphQL request body](server/scripts/search-albums-perf.json) to -the API, for example: +Use a separate test database, not the shared catalog. Apply migrations, run the large seed and start the API. The SQL checks and GraphQL request are committed in the repository. + +The scripts read `server/.env`, but `psql` needs `DATABASE_URL` exported in your shell (for example, `export DATABASE_URL='postgres://...'` in Bash). ```bash -psql "$DATABASE_URL" -f server/scripts/perf-check.sql +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -c "ANALYZE;" +psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f server/scripts/perf-check.sql curl -s -o /dev/null -w 'total %{time_total}s\n' \ -H 'content-type: application/json' \ --data @server/scripts/search-albums-perf.json \ http://localhost:3001/graphql ``` -On 16 October 2026, using a local PostgreSQL 16 container with 100,000 -generated albums, seeding completed in **8.42 s**. Representative timings were: +Run the HTTP request once without `-o /dev/null` to check that GraphQL returns data without errors before measuring response time. On Windows, use `curl.exe` and `NUL` instead of `/dev/null`. + +In local testing on 16 October 2026, using PostgreSQL 16 and 100,000 generated albums, seeding completed in **8.42 seconds**. Representative timings were: | Operation | Execution time | | ------------------------------------------------------------------ | -------------: | @@ -120,63 +125,39 @@ generated albums, seeding completed in **8.42 s**. Representative timings were: | Two-genre AND filter plus two decades, ordered by title | 19.57 ms | | Complete broad `searchAlbums` GraphQL query with facets and paging | 34.65 ms | -The first three use the production search queries against all 100,000 rows; -the last timing is the HTTP response for the GraphQL resolver. Repeat this -check on the VM after deploying the backend, because its hardware and -PostgreSQL version may differ. +The first three timings are SQL queries; the last is a GraphQL HTTP response. These results were measured locally, not on the VM, and may differ on other hardware. ## Project status -| Milestone | Due | Delivers | -| --------- | ------ | ----------------------------------------------------------------------------------------------------- | -| M1 | 2 Oct | Spec and mockup: GraphQL schema, data model, architecture ADRs, UI specification, workflow and AI log | -| M2 | 16 Oct | PostgreSQL + GraphQL backend on the VM, client interaction | -| M3 | 30 Oct | Beta: search, paging, sorting, filtering, accessibility, bærekraft | -| M4 | 13 Nov | Finished prototype and the full test suite | +| Milestone | Due | Delivers | +| --------- | ------ | ---------------------------------------------------------------------- | +| M1 | 2 Oct | Schema, data model, architecture, UI specification and workflow | +| M2 | 16 Oct | PostgreSQL and GraphQL backend on the VM, initial frontend interaction | +| M3 | 30 Oct | Search UI improvements, accessibility and sustainability | +| M4 | 13 Nov | Finished prototype and full test suite | ### Where the code is -| Path | What it is | -| ---------------- | --------------------------------------------------------------------------------------------------------------- | -| `client/` | React + Vite + Apollo Client. Album and artist search connected to the GraphQL API | -| `server/` | GraphQL Yoga + Drizzle over PostgreSQL. `searchAlbums` with filtering, facets and keyset paging. Live on the VM | -| `schema.graphql` | The contract both sides work against | -| `docs/` | Architecture and data model ADRs, UI specification, workflow, AI log, VM runbook | - -**Implemented:** PostgreSQL database with tables for albums, artists, genres, -tracks, users, reviews and crate items. The GraphQL backend supports album -searches, genre and decade filtering, pagination and sorting by `RELEVANCE`, -`TITLE_ASC` and `RELEASE_YEAR_DESC`. The schema also defines -`AVERAGE_RATING_DESC`, but this currently returns an error until reviews are -implemented. - -The database tables for reviews and crate items exist, but the corresponding -GraphQL functionality is not yet implemented. - -The frontend supports searching by album title or artist name, with a 300 ms -debounce to avoid unnecessary requests. Users can also submit a search -immediately using Enter or the Search button. The interface provides loading -feedback, error handling and a message when no albums are found. - -Currently, the frontend displays the first 20 matching albums, sorted by -relevance. Filtering, sorting, pagination controls and album details are not -yet available in the UI. - -**Planned:** filters and sorting in the UI, the reviews and wantlist -mutations, accessibility and bærekraft (M3), then the Playwright `e2e/` -suite (M4). The GraphQL operations behind those are in `schema.graphql`, -but `searchAlbums` is the only query with a resolver so far, along with the -`Album` fields it returns. - -Simulated user identity is defined in the GraphQL schema and architecture -documentation, but runtime handling of `X-User-Id` is not yet implemented. +| Path | What it is | +| ---------------- | ----------------------------------------------------------- | +| `client/` | React, Vite and Apollo Client frontend | +| `server/` | GraphQL Yoga API and PostgreSQL database access | +| `schema.graphql` | GraphQL schema | +| `docs/` | Architecture, data model, UI, workflow, AI log and VM guide | + +**Implemented:** The PostgreSQL database includes albums, artists, genres, tracks, users, reviews and crate items. The GraphQL backend supports album and artist search, genre and decade filters, sorting and cursor-based pagination. Supported sorts are `RELEVANCE`, `TITLE_ASC` and `RELEASE_YEAR_DESC`. `AVERAGE_RATING_DESC` is defined in the schema but currently returns an error because reviews are not implemented yet. + +The frontend supports searching by album title or artist, with a 300 ms debounce, Enter/Search submission, loading and error feedback, and a no-results message. It currently shows the first 20 matches ordered by relevance. + +**Planned:** Frontend filters, sorting, pagination controls and album details; reviews, ratings and wantlist functionality; accessibility and sustainability work (M3); and Playwright end-to-end tests (M4). The database tables for reviews and crate items exist, but their GraphQL functionality is still planned. `X-User-Id` is also defined but not yet handled at runtime. + +### Backend search behavior + +The API uses PostgreSQL full-text search for album titles and artist names. It supports genre and decade filters, relevance/title/release-year sorting, facet counts and cursor-based pagination. These capabilities are available through GraphQL but are not all exposed in the frontend yet. ## Try the application -Open or your local frontend and try -these searches. Expected counts depend on the sample catalog from -`pnpm --filter @t19/server db:seed`; a different database may return different -results. +Open http://it2810-19.idi.ntnu.no/project2/ or the local frontend and try these searches. Expected counts assume the 16-album sample catalog; other data may produce different results. | Search | Expected matches | Albums | | ----------- | ---------------- | ------------------------------------------ | @@ -188,39 +169,19 @@ Search is case-insensitive: `BLUE` should return the same matches as `blue`. ## Infrastructure -The database and the GraphQL API run directly on the group VM, not in -containers and not on an external service. Live at -, GraphQL on port 3001 behind an -Apache `ProxyPass`. PostgreSQL is local to the machine and is not exposed. +PostgreSQL and the GraphQL API run directly on the group VM. Apache serves the frontend at http://it2810-19.idi.ntnu.no/project2/ and proxies `/graphql` to the API on port 3001. The API runs as the `crate-api.service` systemd service. -The API is a systemd unit, `crate-api.service`, running the built `server/` -from `~/T19-Project-2`. It loads `DATABASE_URL` from that checkout's -`.env`, which is gitignored and so survives a deploy. - -CI runs on a self-hosted runner on the same VM. Pushes to `main` run the -quality gates, then deploy the client bundle to Apache and the built API to -the checkout `crate-api` runs from. +CI uses a self-hosted runner on the VM. Pushes to `main` run the project checks and deploy the frontend and backend. See [VM setup](docs/VM-setup.md) for deployment and verification steps. ## Working in this repository -`main` is protected - you cannot push to it directly. Work on a branch and -open a pull request: +`main` is protected. Create a branch and open a pull request: ```bash git checkout -b my-feature git push -u origin my-feature ``` -Before a pull request can be merged: - -1. **A member of IT2810-H26-T19 must approve it.** You cannot approve your own pull - request, and approvals from staff do not count - you review each other's - code. -2. **Every review comment must be marked resolved.** Reply to each one, fix it - or explain why not, then click _Resolve conversation_. -3. **Pushing new commits dismisses the approval**, so if you change something - after being approved, ask for another review. This is deliberate: what was - approved is what gets merged. +Before merging, a group member must approve the PR and all review conversations must be resolved. New commits dismiss earlier approvals, so request another review after pushing changes. -Branch naming, commit style, PR size and review expectations are in -[`docs/WORKFLOW.md`](docs/WORKFLOW.md). +See [`docs/WORKFLOW.md`](docs/WORKFLOW.md) for branch naming, commit conventions and review expectations. diff --git a/docs/AI-USAGE.md b/docs/AI-USAGE.md index 97f3658..5e08f4d 100644 --- a/docs/AI-USAGE.md +++ b/docs/AI-USAGE.md @@ -54,6 +54,7 @@ the part that matters most. | 2026-10-15 | robertky | Codex (ChatGPT) | Assisted me when I designed and implemented #7's batched synthetic catalog generator, its tests and local performance verification. I reviewed the generated data, when AI suggested something before using it. | | 2026-10-16 | rachelks | Codex (ChatGPT) | Assisted with updating README and VM setup documentation for #48, clarifying implemented functionality and local development instructions. | | 2026-10-16 | evenkkl | Claude | Assisted with reviewing PRs #50 and #51, checking the changes against the code and identifying areas for improvement. | +| 2026-10-16 | rachelks | Codex (ChatGPT) | Assisted with a final documentation review and cleanup for #48. | ## Notes diff --git a/docs/VM-setup.md b/docs/VM-setup.md index 33b438b..d930c91 100644 --- a/docs/VM-setup.md +++ b/docs/VM-setup.md @@ -1,87 +1,132 @@ # VM setup -How to run the GraphQL API (`server/`) on the group VM and check that it works. +How to deploy the Crate application on the group VM and verify that it works. -## Run the API +## Deployment setup -`DATABASE_URL` must point at the `crate` database, for example in `server/.env`: +The VM runs PostgreSQL 18 (database `crate`, role `t19`), Node.js 24, pnpm, +Apache and the `crate-api.service` systemd service. The application is deployed +from `~/T19-Project-2`. -``` +The repository root `.env` file must contain the database connection URL: + +```dotenv DATABASE_URL=postgres://t19:@127.0.0.1:5432/crate ``` +This file is gitignored and must not be committed. Unlike local development, +which uses `server/.env`, the VM service and deployment use the root `.env`. + +To manually update the API, run these commands from `~/T19-Project-2`: + ```bash -pnpm install -pnpm --filter @t19/server db:migrate # create the catalog tables +pnpm install --frozen-lockfile +export DATABASE_URL="$(sed -n 's/^DATABASE_URL=//p' .env | tail -n 1)" +pnpm --filter @t19/server db:migrate pnpm --filter @t19/server build -pnpm --filter @t19/server start # http://localhost:3001/graphql +sudo /usr/bin/systemctl restart crate-api +``` + +The database and role must already exist before migrations are run. The API +runs on port 3001 and needs the repository's `schema.graphql` file at startup. + +### Automated deployment + +Pushes to `main` trigger `.github/workflows/ci.yml`. After formatting, linting, +type-checking, tests and builds pass, CI deploys the frontend to +`/var/www/html/project2`, updates the API in `~/T19-Project-2`, applies pending +migrations and restarts `crate-api.service`. Deployment does not seed the database. + +Apache serves the frontend at `/project2/` and forwards `/graphql` requests to +the API on port 3001: + +```apache +ProxyPass /graphql http://127.0.0.1:3001/graphql +ProxyPassReverse /graphql http://127.0.0.1:3001/graphql ``` +### Verify a deployment + +On the VM, check the deployed commit, service status and frontend response: + +```bash +git -C ~/T19-Project-2 rev-parse HEAD +systemctl is-active crate-api +journalctl -u crate-api -n 50 --no-pager +curl -fsS -o /dev/null -w '%{http_code}\n' http://localhost/project2/ +``` + +Then run the smoke tests below against the API and, if needed, through Apache. +You can also open and try an album search. + ### Optional sample data -To reset the database with the small sample catalog: +To reset a **development database** to the sample catalog: ```bash pnpm --filter @t19/server db:seed ``` -**Warning:** This replaces the existing catalog and deletes associated -reviews and crate items. Do not run it against a database containing data -you want to keep. +**Warning:** Seeding replaces the catalog and deletes associated tracks, +reviews and crate items. Do not run it on a database with data you want to keep. +The command requires the same `DATABASE_URL` environment variable as migrations. ## Frontend GraphQL endpoint -The frontend uses Apollo Client with `/graphql` as its default endpoint. -On the VM, Apache proxies `/graphql` to the API on port 3001; the frontend's -`/project2/` asset base does not change that endpoint. +The frontend uses Apollo Client and sends requests to `/graphql`. During local +development, Vite proxies these requests to the backend on port 3001. On the VM, +Apache handles the proxy instead. The frontend is served under `/project2/`. -For local development, run `pnpm --filter @t19/server dev` and `pnpm dev` in -separate terminals, after configuring the database and applying migrations. -Open . Vite proxies `/graphql` to -`http://127.0.0.1:3001`, so both processes must be running. To override the -frontend endpoint, set `VITE_GRAPHQL_URL` in `client/.env.local`. +For local setup instructions, see the [README](../README.md). To use another API +endpoint, set `VITE_GRAPHQL_URL` in `client/.env.local`. ## Smoke suite -Run on the VM after a deploy. Each command prints what to expect. The -expectations assume the sample data from `db:seed`. +Run these commands on the VM after deployment. The expected counts below assume +the sample catalog from `db:seed`; counts may differ if the database contains +other data. Check each response for GraphQL `errors`. ```bash API=http://localhost:3001/graphql gql() { curl -s "$API" -H 'content-type: application/json' -d "$1"; echo; } ``` -1. The API answers. Expect `{"data":{"__typename":"Query"}}`. +To test Apache routing as well, set `API=http://localhost/graphql` and repeat. -```bash -gql '{"query":"{ __typename }"}' -``` +1. **API response.** Expect `{"data":{"__typename":"Query"}}`. -2. Search is case-insensitive. Expect `totalCount` 4 for both. + ```bash + gql '{"query":"{ __typename }"}' + ``` -```bash -gql '{"query":"{ a: searchAlbums(input:{query:\"blue\",sort:RELEVANCE},first:5){ totalCount } b: searchAlbums(input:{query:\"BLUE\",sort:RELEVANCE},first:5){ totalCount } }"}' -``` +2. **Case-insensitive search.** With sample data, both `blue` and `BLUE` should + return `totalCount: 4`. -3. A single word also matches artist names, and artists and genres resolve. - Expect A Love Supreme and Blue Train, both by John Coltrane. + ```bash + gql '{"query":"{ a: searchAlbums(input:{query:\"blue\",sort:RELEVANCE},first:5){ totalCount } b: searchAlbums(input:{query:\"BLUE\",sort:RELEVANCE},first:5){ totalCount } }"}' + ``` -```bash -gql '{"query":"{ searchAlbums(input:{query:\"coltrane\",sort:TITLE_ASC},first:5){ totalCount edges { node { title releaseYear artists { name } genres { name } } } } }"}' -``` +3. **Artist search.** Searching for `coltrane` should return _A Love Supreme_ + and _Blue Train_, both by John Coltrane. -4. Facets. With Rock (id 4) selected, expect `totalCount` 7. Genres combine - with AND, so `genreFacets` counts albums that are Rock and that genre: - Rock 7, Pop 3, Electronic 2. `decadeFacets` counts only Rock albums. + ```bash + gql '{"query":"{ searchAlbums(input:{query:\"coltrane\",sort:TITLE_ASC},first:5){ totalCount edges { node { title releaseYear artists { name } genres { name } } } } }"}' + ``` -```bash -gql '{"query":"{ searchAlbums(input:{query:\"\",genreIds:[\"4\"],sort:TITLE_ASC},first:5){ totalCount genreFacets { genre { name } count } decadeFacets { decade count } } }"}' -``` +4. **Genre filters and facets.** With Rock (ID `4`) selected, the sample catalog + should return `totalCount: 7`. Genre facets should show Rock `7`, Pop `3` + and Electronic `2`. The `decadeFacets` counts should also reflect only albums + matching the Rock filter. -5. Paging. Expect `hasNextPage` true and an `endCursor`. Paste the cursor in as - `after` to get the next two albums, with no album repeated. + ```bash + gql '{"query":"{ searchAlbums(input:{query:\"\",genreIds:[\"4\"],sort:TITLE_ASC},first:5){ totalCount genreFacets { genre { name } count } decadeFacets { decade count } } }"}' + ``` -```bash -gql '{"query":"{ searchAlbums(input:{query:\"\",sort:RELEASE_YEAR_DESC},first:2){ edges { node { title releaseYear } } pageInfo { hasNextPage endCursor } } }"}' -gql '{"query":"{ searchAlbums(input:{query:\"\",sort:RELEASE_YEAR_DESC},first:2,after:\"\"){ edges { node { title releaseYear } } pageInfo { hasNextPage endCursor } } }"}' -``` +5. **Pagination.** The first query should return two albums, `hasNextPage: true` + and an `endCursor`. Replace `` in the second query with that value; + the next page should contain different albums. + + ```bash + gql '{"query":"{ searchAlbums(input:{query:\"\",sort:RELEASE_YEAR_DESC},first:2){ edges { node { title releaseYear } } pageInfo { hasNextPage endCursor } } }"}' + gql '{"query":"{ searchAlbums(input:{query:\"\",sort:RELEASE_YEAR_DESC},first:2,after:\"\"){ edges { node { title releaseYear } } pageInfo { hasNextPage endCursor } } }"}' + ``` diff --git a/docs/WORKFLOW.md b/docs/WORKFLOW.md index 1a4a7d4..765e7fb 100644 --- a/docs/WORKFLOW.md +++ b/docs/WORKFLOW.md @@ -57,7 +57,8 @@ Then run: pnpm format:check && pnpm lint && pnpm typecheck && pnpm test ``` -All four must pass. CI runs the same thing and will reject the PR otherwise. +All four must pass. CI also runs `pnpm build` and checks the client bundle +budget; a failed gate blocks merging. ## Milestones diff --git a/docs/architecture.md b/docs/architecture.md index 959dc7a..ffe9aca 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -5,6 +5,8 @@ in M2 and M3. The API contract is `schema.graphql`, the data model is in `docs/data-model.md`, and the planned interface is described in `docs/ui-specification.md`. +At M2, search, filtering, sorting, pagination and DataLoaders are implemented in the backend. User identity, reviews, crate functionality and URL-based search state are planned for later milestones. + ## 1. PostgreSQL + GraphQL Yoga **Context:** The data is relational. Albums have many artists and genres, and @@ -35,7 +37,7 @@ the next page continues from there (`first` + `after` in the schema). `OFFSET` reads and throws away every skipped row, so it gets slower on later pages, and it can repeat or skip rows when data changes between requests. -**Consequences:** Paging stays efficient on later pages. Paging only goes +**Consequences:** Keyset paging avoids scanning skipped rows, although counts and facets can still affect query performance. Paging only goes forward, so there is no "jump to page N". Each sort mode needs its own cursor logic, including nullable fields such as release year. @@ -55,6 +57,8 @@ resolver loads every match into memory. builder is the most complex server code, so it needs tests against a real database. +The implemented search uses PostgreSQL full-text search with weighted album titles and artist names. Genre filters combine with AND, while decade filters combine with OR. Counts and facets cover matching results, not just the current page. The supported sort options are `RELEVANCE`, `TITLE_ASC` and `RELEASE_YEAR_DESC`; rating-based sorting is not yet implemented. + ## 4. DataLoaders against N+1 **Context:** GraphQL resolves each field on its own. A page of 20 albums asking diff --git a/docs/data-model.md b/docs/data-model.md index 3708cb2..7cc88e5 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -1,8 +1,6 @@ # 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. +This document describes the model behind `schema.graphql`. The M2 database migrations implement the catalog and user-related tables. Currently, only album search and artist/genre relations are available through GraphQL resolvers. The user interactions described below are planned for later milestones. ## Core catalog @@ -14,7 +12,14 @@ queries are deliberately left to M2. - 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. +The two many-to-many relationships use `album_artist` and `album_genre`, with +composite primary keys. Tracks have a unique `(album_id, track_number)`. +Foreign keys cascade on deletion. The seeds populate only the album, artist, +genre and join tables. + +`album.search_vector` has a GIN index. SQL triggers refresh weighted title and +artist terms when titles, artist names or artist links change. Migration +`0001` also backfills existing albums. ## User-generated data @@ -33,8 +38,10 @@ The two many-to-many relationships will use join tables in PostgreSQL in M2. 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. See +The tables exist, with unique `(user_id, album_id)` pairs for reviews and +crate items, a rating check of 1-5, and a trigger updating `review.updated_at`. +Identity transport, display-name derivation and review/crate resolvers are +not implemented. The schema describes the operations the client will need. See [`docs/architecture.md`](./architecture.md) decision 5 for how the identity travels and what happens when it is missing. @@ -44,3 +51,5 @@ travels and what happens when it is missing. 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 and decade facet counts for the filter UI. + +Genres combine with AND, while decades combine with OR. Filtering, sorting and pagination are handled in SQL. See the README for supported sort options and pagination details.