feat(ADM-018): completed feature

This commit is contained in:
chattie
2026-08-17 22:23:10 +02:00
parent cf1c69fc8b
commit d595b4871f
871 changed files with 47411 additions and 281 deletions

View File

@@ -0,0 +1,65 @@
# Architect — F-011 Product images
## Decision
Keep product images inside the catalog module. Product and variant images are catalog-owned presentation metadata, not a separate bounded context yet.
## Data model
Add `catalog_product_images`:
- `id uuid primary key default gen_random_uuid()`
- `product_id uuid not null references catalog_products(id) on delete cascade`
- `variant_id uuid null references catalog_product_variants(id) on delete cascade`
- `url text not null`
- `alt_text text not null`
- `position integer not null`
- `role text not null check (role in ('main', 'gallery'))`
- timestamps
Rules:
- `position >= 0`
- `variant_id` is optional; when present it must belong to the same `product_id`.
- Exactly one `main` image per product-level set and per variant-level set. Implement with partial unique indexes:
- `(product_id) WHERE variant_id IS NULL AND role = 'main'`
- `(variant_id) WHERE variant_id IS NOT NULL AND role = 'main'`
- Stable ordering by `position`, then `created_at`, then `id`.
## Domain/application
Add image domain types in catalog:
- `ProductImageRole = 'main' | 'gallery'`
- `ProductImage` with productId, optional variantId, url, altText, position, role.
- `NewProductImage` and `ProductImagePatch`.
Add `ProductImageRepository` port. It is the boundary that makes storage swappable:
- API/application depend only on the port.
- Local-first storage is represented as URLs accepted by the adapter/repository path for this slice.
- Future CDN/image processing must touch infrastructure only.
Use cases:
- `ListProductImages(productId, variantId?)`
- `AttachProductImage(productId, input)`; verify product exists, and if `variantId` is passed verify it belongs to that product.
- `DetachProductImage(productId, imageId)`
- `ReorderProductImages(productId, items)`; validates all listed images belong to the same product/scope.
## API
Admin mutations, public read through product serialization:
- `GET /productos/:slug` includes ordered `images` list for product-level images.
- `GET /products/search` may continue returning product core only unless cheap to include images; acceptance only requires product exposes ordered image list.
- `GET /products/:id/images` returns ordered product-level images.
- `POST /products/:id/images` attaches product or variant image. Body: `url`, `altText`, `role`, optional `variantId`, optional `position`.
- `DELETE /products/:id/images/:imageId` detaches.
- `PATCH /products/:id/images/reorder` accepts ordered `{ imageId, position }[]`.
Validation:
- URL must be a bounded string and should be parseable URL or local absolute path beginning with `/`.
- `altText` is required, trimmed, max 300.
- `role` uses the domain enum.
## Tests/evidence
- Unit test use cases with fake repositories:
- ordered list preserves repository ordering.
- attach rejects missing product.
- attach rejects variant from another product.
- Repository/migration shape can be covered by existing migration integration test when `TEST_DATABASE_URL` exists.
- `npm test`, `npm run typecheck`, and root `./scripts/verify.sh` must be green.
## Out of scope guardrails
No binary upload, CDN, resizing, optimization pipeline, or external object-store SDK in F-011. No new runtime dependency expected.

View File

@@ -0,0 +1,19 @@
# Documenter — F-011 Product images
## Documentation updated
- Updated `project/README.md` catalog section to include product images as catalog-owned data.
- Documented image routes:
- `GET /products/:id/images`
- `POST /products/:id/images`
- `DELETE /products/:id/images/:imageId`
- `PATCH /products/:id/images/reorder`
- Documented image persistence and behavior:
- `catalog_product_images`
- ordered image metadata with `url`, `altText`, `position`, `role`
- product-level and variant-level images
- storage adapter boundary
- no binary upload, CDN, or processing pipeline in this slice
## Evidence
- `cd project && npm run lint` — PASS after README update.
- `./scripts/verify.sh` — PASS after README update.

View File

@@ -0,0 +1,38 @@
# Implementer — F-011 Product images
## Summary
Implemented product image support inside the catalog module with a swappable storage boundary, PostgreSQL persistence, admin image endpoints, and product serialization that exposes ordered images with alt text.
## Code changes
- Added image domain model and storage port: `project/src/modules/catalog/domain/image.ts`.
- Added product image repository port: `project/src/modules/catalog/domain/ports.ts`.
- Added use cases: `project/src/modules/catalog/application/image-use-cases.ts`.
- Added local-first URL storage adapter: `project/src/modules/catalog/infrastructure/local-product-image-storage.ts`.
- Added PostgreSQL image repository: `project/src/modules/catalog/infrastructure/pg-product-image-repository.ts`.
- Added migration: `project/migrations/009_catalog_product_images.js`.
- Extended catalog routes: `project/src/modules/catalog/api/catalog.routes.ts`.
- Added unit coverage: `project/src/modules/catalog/tests/image-use-cases.test.ts`.
## API behavior
- `GET /productos/:slug` now returns product-level `images`, ordered by repository ordering, with `url`, `altText`, `position`, and `role`.
- `GET /products/:id/images` lists product-level images.
- `POST /products/:id/images` attaches an image. Admin-only. Body: `url`, `altText`, `role`, optional `variantId`, optional `position`.
- `DELETE /products/:id/images/:imageId` detaches an image. Admin-only.
- `PATCH /products/:id/images/reorder` reorders images. Admin-only.
## Data rules
- Images can belong to a product or a product variant.
- Variant images must reference a variant that belongs to the same product.
- One `main` image is allowed per product-level scope and per variant scope.
- Images are ordered by `position`, then `created_at`, then `id`.
## Evidence
- `cd project && npm run lint` — PASS.
- `cd project && npm run typecheck` — PASS.
- `cd project && npm test` — PASS: 17 files passed, 7 skipped; 64 passed, 33 skipped.
- `./scripts/verify.sh` — PASS.
## Notes
- No new npm dependencies.
- No binary upload, CDN, resize/optimization pipeline, or external object-store SDK added.
- Integration tests remain skipped without `TEST_DATABASE_URL`, consistent with prior features.

View File

@@ -0,0 +1,15 @@
{
"feature_id": "F-011",
"agent": "leader",
"verdict": "APPROVED",
"summary": "F-011 closed with reviewer, security, and QA gates approved. Product images are implemented in catalog with storage boundary, migration, routes, tests, and README documentation.",
"evidence": [
"work/artifacts/F-011/reviewer.json verdict APPROVED",
"work/artifacts/F-011/security.json verdict APPROVED",
"work/artifacts/F-011/qa.json verdict APPROVED",
"work/artifacts/F-011/documenter.md present",
"./scripts/verify.sh final close run: PASS",
"cd project && npm run lint/typecheck/test: PASS in build evidence"
],
"timestamp": "2026-08-15T14:43:57Z"
}

View File

@@ -0,0 +1,15 @@
{
"feature_id": "F-011",
"agent": "qa",
"verdict": "APPROVED",
"summary": "QA approved. Acceptance criteria are covered: product serialization exposes ordered image metadata with alt text, storage is behind an infrastructure adapter/port, and verification is green.",
"evidence": [
"Acceptance: Product exposes ordered image list with alt text -> covered by ListProductImages unit test and GET /productos/:slug serialization path",
"Acceptance: Swapping storage adapter touches only infrastructure layer -> ProductImageStorage port plus LocalProductImageStorage injected into PgProductImageRepository",
"Acceptance: verify.sh green -> ./scripts/verify.sh PASS",
"cd project && npm test -- src/modules/catalog/tests/image-use-cases.test.ts: PASS (3 tests)",
"cd project && npm run typecheck: PASS",
"Full build evidence from implementer: lint/typecheck/test/verify all PASS"
],
"timestamp": "2026-08-15T14:42:34Z"
}

View File

@@ -0,0 +1,16 @@
{
"feature_id": "F-011",
"agent": "reviewer",
"verdict": "APPROVED",
"summary": "Product images implementation matches F-011 scope: catalog-owned image domain, repository port/storage boundary, PostgreSQL migration, admin attach/detach/reorder endpoints, public product images serialization, and unit tests. A pre-gate concern around mixed-scope reorder validation was corrected before approval.",
"evidence": [
"Read work/current.md and work/artifacts/F-011/architect.md/implementer.md",
"Inspected catalog image domain/use cases/repository/routes/migration/test files",
"Verified no new runtime dependency was added",
"cd project && npm run lint: PASS",
"cd project && npm run typecheck: PASS",
"cd project && npm test: PASS (64 passed, 33 skipped)",
"./scripts/verify.sh: PASS"
],
"timestamp": "2026-08-15T14:42:03Z"
}

View File

@@ -0,0 +1,15 @@
{
"feature_id": "F-011",
"agent": "security",
"verdict": "APPROVED",
"summary": "Security gate approved. F-011 stores image URLs only, adds no upload/parser/CDN dependency, keeps admin mutations behind existing authenticate + RBAC, validates URL/alt text/role/position inputs, and enforces product/variant scope in application and database.",
"evidence": [
"Checked admin-only POST/DELETE/PATCH image routes use authenticate + requireRole(admin)",
"Checked public GET product image exposure only serializes stored metadata: url, altText, position, role, ids, timestamps",
"Checked migration constraints: role check, non-negative position, product/variant FKs, variant scope trigger, unique main image indexes",
"cd project && npm audit --audit-level=high --omit=dev: found 0 vulnerabilities",
"rg secret patterns over project/src/modules/catalog and migration 009: no findings",
"No new npm dependencies or credentials introduced"
],
"timestamp": "2026-08-15T14:42:18Z"
}