Files
mercadodevida/work/artifacts/F-011/architect.md
2026-08-17 22:23:10 +02:00

3.2 KiB

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.