feat(ADM-018): completed feature
This commit is contained in:
65
work/artifacts/F-011/architect.md
Normal file
65
work/artifacts/F-011/architect.md
Normal 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.
|
||||
19
work/artifacts/F-011/documenter.md
Normal file
19
work/artifacts/F-011/documenter.md
Normal 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.
|
||||
38
work/artifacts/F-011/implementer.md
Normal file
38
work/artifacts/F-011/implementer.md
Normal 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.
|
||||
15
work/artifacts/F-011/leader-close.json
Normal file
15
work/artifacts/F-011/leader-close.json
Normal 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"
|
||||
}
|
||||
15
work/artifacts/F-011/qa.json
Normal file
15
work/artifacts/F-011/qa.json
Normal 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"
|
||||
}
|
||||
16
work/artifacts/F-011/reviewer.json
Normal file
16
work/artifacts/F-011/reviewer.json
Normal 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"
|
||||
}
|
||||
15
work/artifacts/F-011/security.json
Normal file
15
work/artifacts/F-011/security.json
Normal 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"
|
||||
}
|
||||
Reference in New Issue
Block a user