Skip to main content

The prompt

title: "Scaffold the product module and its resources"
context: |
  Build a `product` domain: catalog items a user (vendor) owns and an admin
  manages for anyone. Auth is owner-or-admin on every mutation. A product is
  classified by an optional `Category` and any number of `Tag`s from the shared
  taxonomy modules (not free text). Visibility ties to lifecycle: only a
  published product is visible to others — drafts stay private to owner and
  admins. If `modules/product/` already exists, this work is void — do not run.
goal: |
  Create the `product` module + needed resources, wired owner-or-admin on mutations.

  ## Notes
  - If `modules/product/` exists, STOP and report. Else `/module:create` product,
    then build each resource via its `*:create` skill (`--module=product`),
    respecting controllers → services → repositories → entities, registering all.
  - Judge each resource; create the justified, skip the rest with a reason.
    Defaults: entity + repository always; service + controller per use case;
    permission always (owner-or-admin on update/delete, reuse the permission
    service); storage for product images if uploaded (remove on delete);
    migration/seed/event/translation/queue only if applicable (name/description
    are translation candidates; route publish/reindex through a queue if used);
    workflow only if publishing has steps that must roll back together (e.g.
    search index / payment sync), else skip.
  - Classify with the shared taxonomy: optional `Category` relation + `Tag`
    many-to-many, links owned here. Wire each only if its module exists, else skip
    and report.
  - Enforce uniqueness on `slug` and `sku`; throw a typed conflict
    (`ProductAlreadyExistsException`).
  - Visibility by lifecycle: non-owner/non-admin reads/lists only published;
    owner and admins also see drafts.
  - Reject any update dropping `stock` below zero.
  - Throw typed exceptions (e.g. `ProductNotFoundException`), never return null.

  ### Data Model
  - `Product.owner` ↔ `User.products` — ManyToOne / OneToMany
  - `Product.category` → `Category` — ManyToOne (owned here, nullable)
  - `Product.tags` → `Tag` — ManyToMany (owned here)
dod: |
  - [ ] Aborts with a report if `modules/product/` exists
  - [ ] `product` module created and registered into the app and `SharedModule`
  - [ ] `Product` entity with fields: `name`, `slug` (unique), `sku` (unique),
    `description`, `icon`, `color` (`SimpleColorType`), `thumbnail`, `images`,
    `price`, `compareAtPrice` (nullable), `currency` (ISO 4217), `stock`,
    `weight` (nullable), `status` (`StatusType`, draft → active → archived),
    `publishedAt` (nullable), `lang` (`LocaleType`), `category`, `tags`, `owner`,
    `createdAt`, `updatedAt`
  - [ ] Full CRUD; user mutates only their own, admin any; non-owner/non-admin
    rejected on mutation and reads/lists only published
  - [ ] Duplicate slug or sku rejected; category/tags reference the shared
    taxonomy, not free text
  - [ ] Update dropping stock below zero rejected; delete removes stored images
  - [ ] If queues are used, publish/reindex work runs through a queue
  - [ ] Unneeded resources skipped and reported with a reason
  - [ ] `bun run fmt`, `bun run lint`, `bun run test` pass from the root