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

4.2 KiB

Architect — F-008 Catalog core: products domain

done -> work/artifacts/F-008/architect.md

Deliverables

  • src/modules/catalog/ with domain, application, infrastructure and api layers.
  • PostgreSQL migration for catalog_products and catalog_product_categories.
  • Unit and integration tests covering product state filtering, slug conflicts, category assignment, public slug URL behavior and module boundaries.

Key decisions

  1. Catalog owns products only: product rows live in catalog_products; assignment join table lives in catalog_product_categories because the relationship is product-owned for F-008. Categories remain independent and are referenced only by ID at the database boundary.
  2. Domain is pure: src/modules/catalog/domain/* must import no pg, no Fastify, no HTTP, no repository implementations. Acceptance explicitly requires zero database/HTTP imports.
  3. Product state is explicit: allowed states are draft, active, archived. Public list/search and slug reads must return only active products.
  4. Slug as public identity: API exposes /productos/<slug>; internal UUID may be returned in admin responses but must never be required for public product URLs.
  5. SearchProducts v1 is PostgreSQL-backed but interface-shaped: implement simple text matching over name and description, active-only for public use. This is not the dedicated FTS module from F-012; keep it boring and replaceable.
  6. Category assignment validation: creating/updating product category IDs must reject unknown categories. Runtime validation may query categories_categories from the catalog repository only to enforce referential integrity for the join. Do not import the categories module internals.
  7. No variants, stock, prices: product core fields are identity, name, slug, description, state, SEO metadata, timestamps and category IDs. Anything sellable, stock-related or price-related belongs to later tickets.
  8. No new dependencies: current stack already covers validation, PostgreSQL and testing.

Suggested API contract

  • GET /productos/:slug → public active product by slug; 404 for draft/archived/missing.
  • GET /products/search?q=&limit=&offset= → public active product search/listing; only active products.
  • POST /products → admin-only create product with optional category IDs; duplicate slug returns 409.
  • PATCH /products/:id → admin-only update product fields/state/category IDs.

Domain model

  • ProductState = 'draft' | 'active' | 'archived'.
  • Product: id, name, slug, description, state, seoTitle, seoDescription, categoryIds, createdAt, updatedAt.
  • NewProduct: same editable fields except generated id/timestamps; state defaults to draft; categoryIds defaults to empty.
  • ProductPatch: optional editable fields; if categoryIds is present it replaces assignments.

Error mapping

  • Duplicate slug → 409 PRODUCT_SLUG_EXISTS.
  • Unknown category ID → 422 PRODUCT_CATEGORY_NOT_FOUND.
  • Missing product → 404 NOT_FOUND.
  • Invalid state/payload → existing 400 VALIDATION_ERROR from Zod.

Test plan

  • Unit: domain/application SearchProducts returns only active products.
  • Unit: domain layer imports remain pure via boundary lint and targeted source scan.
  • Integration/API: duplicate slug create returns HTTP 409.
  • Integration/API: draft products do not appear in public search/listing and are not readable by /productos/:slug.
  • Integration/API: active product is readable at /productos/<slug> and response exposes url with the slug.
  • Integration/API: product can be assigned to an existing category; unknown category rejected.

Security posture

  • Public reads are unauthenticated and active-only.
  • Mutations are admin-only via injected shared auth and requireRole.
  • Never trust product state, price, stock or category information from other modules without server-side validation.
  • SQL must remain parameterized; any dynamic update columns must be whitelisted.

Risks

  • F-008 touches a relationship with categories. Keep coupling at database IDs and repository validation, not TypeScript imports from modules/categories/*.
  • Search in this slice is intentionally simple. Do not overbuild FTS/relevance here; F-012 owns search infrastructure.