55 lines
4.2 KiB
Markdown
55 lines
4.2 KiB
Markdown
# 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.
|