Wave 4 of replacing the fixed-data mock adapter, and the first capability the
mall never had a backend for: the home page's banners, promo tiles, quick links
and floor advert art move out of local arrays.
- four explicit tables (`banners`, `promos`, `quick_links`, `floor_adverts`)
rather than one JSONB payload table, so Postgres enforces each shape
- a migration seeds them from the assets the page already rendered, so the flip
is visually a no-op. Destinations are real routes now: the mock's promo links
pointed at dangling `?category=c1` ids and its first banner used `sort=sales`,
which the catalog API rejects
- `GET /api/content/home` is public and returns the four active, ordered lists,
always including a key so a page can render a missing block
- `GET /api/admin/content` and `PUT /api/admin/content/{kind}` let a platform
admin read everything and replace one kind transactionally, with positions
reindexed from the submitted order and a rejected list changing nothing
- the mall's fixed-data adapter learns `getHomeContent`, and a `content` domain
joins the per-domain switch so the rollback path still renders the page
Verified: 23 backend tests green including six new content tests; all three
frontends build; the home page renders the same four blocks as before, an admin
reorder and deactivation change the rendered carousel, and the fixed-data
rollback renders every block with the backend stopped.
OpenSpec change: openspec/changes/replace-mock-api-wave-4
34 lines
2.2 KiB
Markdown
34 lines
2.2 KiB
Markdown
# Spec Delta
|
|
|
|
## Purpose
|
|
|
|
The storefront's editable marketing content: the home page's banners, promotion tiles, quick links and floor advert art, written by platform admins and read publicly.
|
|
|
|
## ADDED Requirements
|
|
|
|
### Requirement: Public home content
|
|
`GET /api/content/home` SHALL be readable without authentication and SHALL return the active home content as four ordered lists: `banners`, `promos`, `quick_links` and `floor_adverts`. Each entry SHALL carry what the storefront renders: an image URL and a destination URL for banners and promos, a bilingual label and an inline SVG glyph for quick links, and an image URL for floor adverts, whose destination is the floor's own category. Entries flagged inactive SHALL never appear, and each list SHALL be ordered by its stored position.
|
|
|
|
#### Scenario: only active content is served
|
|
- **WHEN** a shopper loads the home content while one banner is marked inactive
|
|
- **THEN** that banner is absent from `banners`, and the remaining entries keep their stored order
|
|
|
|
#### Scenario: an empty kind still answers
|
|
- **WHEN** a kind has no active entries
|
|
- **THEN** the response returns an empty list for it rather than omitting the key or failing, so the page renders without that block
|
|
|
|
### Requirement: Content management
|
|
Platform admins SHALL read all home content with `GET /api/admin/content` and replace one kind at a time with `PUT /api/admin/content/{kind}`, where `kind` is `banners`, `promos`, `quick-links` or `floor-adverts`. A replacement SHALL validate every entry, apply as a single transaction, and reindex positions from the submitted order. A rejected entry SHALL leave the stored content exactly as it was. Writing SHALL require the `platform_admin` role.
|
|
|
|
#### Scenario: replace reorders and reindexes
|
|
- **WHEN** an admin submits the same three banners in a different order
|
|
- **THEN** a subsequent public read returns them in the new order
|
|
|
|
#### Scenario: an invalid entry is rejected atomically
|
|
- **WHEN** an admin submits a list whose second entry is missing its image URL
|
|
- **THEN** the request fails and the previously stored list is unchanged
|
|
|
|
#### Scenario: non-admins cannot write
|
|
- **WHEN** a signed-in customer or shop owner sends a replacement
|
|
- **THEN** the API refuses the write
|