feat(ADM-018): completed feature
This commit is contained in:
54
work/artifacts/F-008/architect.md
Normal file
54
work/artifacts/F-008/architect.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user