feat(F-058): completed feature
This commit is contained in:
99
work/artifacts/F-058/implementer.md
Normal file
99
work/artifacts/F-058/implementer.md
Normal file
@@ -0,0 +1,99 @@
|
||||
# F-058 — Implementer evidence
|
||||
|
||||
## Scope delivered
|
||||
|
||||
Admin `/products` showed a 🌿 leaf emoji for every row, even for products
|
||||
that have an image uploaded. The leaf is the fallback in the table cell when
|
||||
`p.imageUrl` is falsy. Two layers contributed to the bug:
|
||||
|
||||
1. **Backend** — `/catalog/products` (admin list) and `/products/search`
|
||||
(public search) both called `serializeProduct(product)` **without** the
|
||||
`images` argument, so the response always carried `images: []` even
|
||||
when rows existed in `catalog_product_images`. Other endpoints
|
||||
(`/productos/:slug`, `/products/:id`, `/products/:id/images`) already
|
||||
passed the images correctly.
|
||||
2. **Frontend** — `apps/admin/src/app/(dashboard)/products/page.tsx`
|
||||
read `p.imageUrl` (a single string), which the API never returns. The
|
||||
backend instead exposes `images: ProductImage[]`. The `imageUrl` field
|
||||
on the type was dead code.
|
||||
|
||||
## Changes
|
||||
|
||||
### Backend
|
||||
|
||||
`project/src/modules/catalog/domain/ports.ts`
|
||||
- Added `listByProductIds(productIds): Promise<ProductImage[]>` to the
|
||||
`ProductImageRepository` interface.
|
||||
|
||||
`project/src/modules/catalog/infrastructure/pg-product-image-repository.ts`
|
||||
- Implemented `listByProductIds` with a single `ANY($1::uuid[])` query,
|
||||
ordered by `(product_id, position, created_at, id)` so the per-product
|
||||
ordering matches `listByProductId`.
|
||||
|
||||
`project/src/modules/catalog/api/catalog.routes.ts`
|
||||
- New helper `groupImagesByProductId` that buckets images by `productId`.
|
||||
- `/catalog/products` now calls `images.listByProductIds(...)` after the
|
||||
page is fetched and groups the result; `serializeProduct` receives the
|
||||
per-product list (defaulting to `[]` when none).
|
||||
- `/products/search` does the same. Logging of the search duration is
|
||||
preserved.
|
||||
|
||||
`project/src/modules/catalog/tests/image-use-cases.test.ts`
|
||||
- `FakeImageRepository` updated to implement the new interface method
|
||||
(mirrors the SQL semantics: filter by `productId IN set`, sort by
|
||||
position/created/id).
|
||||
|
||||
### Frontend
|
||||
|
||||
`project/apps/admin/src/app/(dashboard)/products/page.tsx`
|
||||
- The product cell now reads `p.images?.[0]?.url` instead of
|
||||
`p.imageUrl`. The 40 px thumbnail URL is computed the same way:
|
||||
`images[0].url.replace('/uploads/', '/uploads/40/')`.
|
||||
|
||||
## Acceptance traceability
|
||||
|
||||
| Acceptance criterion | How it is met |
|
||||
| -------------------- | ------------- |
|
||||
| `GET /api/catalog/products` returns images for products with attached images | The endpoint now calls `images.listByProductIds` and feeds the per-product list to `serializeProduct`. Verified with curl: `images=1 url=/uploads/8223b962...jpg` for the seeded product. |
|
||||
| `GET /api/products/search` returns images for products with attached images | Same fix applied. Verified with curl: `images=1` for the same product. |
|
||||
| Admin `/products` shows 40px thumbnail (not leaf) for products with images | Frontend now reads `images[0].url`; the `uploads/40/<file>` URL is requested, served 200. |
|
||||
| No regression in uploads pipeline | Only added a read query and a frontend prop switch. No changes to `attach`, `detach`, `reorder`, or storage. |
|
||||
| `verify.sh` is green | Exit 0. |
|
||||
|
||||
## Manual verification
|
||||
|
||||
```
|
||||
$ curl /api/catalog/products?limit=5 (admin auth)
|
||||
{"items":[{"name":"Proteina Guisante Ecologica",
|
||||
"images":[{"url":"/uploads/8223b962-642c-4c95-bc2e-37c2d2fad4ac.jpg",
|
||||
"role":"gallery",...}]}, ...]}
|
||||
```
|
||||
|
||||
```
|
||||
$ curl /api/products/search?limit=20 (public)
|
||||
… "Proteina Guisante Ecologica" …
|
||||
images: [{ url: "/uploads/8223b962-642c-4c95-bc2e-37c2d2fad4ac.jpg" }]
|
||||
```
|
||||
|
||||
```
|
||||
$ curl http://192.168.18.93:3004/uploads/40/8223b962-642c-4c95-bc2e-37c2d2fad4ac.jpg
|
||||
HTTP 200, 568 bytes (thumbnail served)
|
||||
```
|
||||
|
||||
## Build verification
|
||||
|
||||
- `npm run typecheck` (project/) — exit 0
|
||||
- `npm test` (project/) — 124 passed, 56 skipped
|
||||
- `npm run build` (project/) — exit 0
|
||||
- `npx tsc --noEmit` (apps/admin, frontend, storefront) — exit 0
|
||||
- `./scripts/verify.sh` — exit 0
|
||||
|
||||
## Files touched
|
||||
|
||||
```
|
||||
project/src/modules/catalog/api/catalog.routes.ts (modified)
|
||||
project/src/modules/catalog/domain/ports.ts (modified)
|
||||
project/src/modules/catalog/infrastructure/pg-product-image-repository.ts (modified)
|
||||
project/src/modules/catalog/tests/image-use-cases.test.ts (modified)
|
||||
project/apps/admin/src/app/(dashboard)/products/page.tsx (modified)
|
||||
```
|
||||
Reference in New Issue
Block a user