Skip to main content

The prompt

title: "Scaffold the event module and its resources"
context: |
  Build an `event` domain: a calendar event a user organizes and others attend.
  The organizer owns it; an admin manages any. (This is a domain entity — a
  scheduled happening with start/end — not the framework's pub/sub event resource
  type, which is one of the resources below.) Auth is owner-or-admin on every
  mutation; an attendee link never grants ownership. An event has a window
  (`startsAt` → `endsAt`) and attendees; its location is either an online
  `meetingUrl` or an in-person `Address` drawn from the organizer's own addresses
  (address module). If `modules/event/` already exists, this work is void — do not run.
goal: |
  Create the `event` module + needed resources, wired owner-or-admin on mutations.

  ## Notes
  - If `modules/event/` exists, STOP and report. Else `/module:create` event, then
    build each resource via its `*:create` skill (`--module=event`), 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); event (pub/sub) a strong fit — emit on create/update/cancel to
    notify attendees, only if something subscribes; queue if the project uses
    queues (route attendee notifications/reminders — fan-out off the request
    thread); migration/seed/translation only if applicable; storage only if the
    cover is an uploaded file; workflow skip (status moves freely).
  - Depends on `address`: confirm `modules/address/` exists, wire an `Address`
    relation; do not redefine address fields.
  - Validate location by mode: online (`isOnline` true) → `meetingUrl`, no
    address; in-person → `address`, no `meetingUrl`. Reject mixed/missing.
  - The referenced `address` must belong to the event's owner; reject another
    user's address.
  - Reject `endsAt` not strictly after `startsAt`. When `capacity` is set, reject
    attendees beyond it (unset = unlimited). Attendees get no update/delete rights.
  - Throw typed exceptions (e.g. `EventNotFoundException`), never return null.

  ### Data Model
  - `Event.owner` ↔ `User.events` — ManyToOne / OneToMany
  - `Event.address` ↔ `Address.events` — ManyToOne / OneToMany (nullable for online)
  - `Event.attendees` ↔ `User.attendingEvents` — ManyToMany
dod: |
  - [ ] Aborts with a report if `modules/event/` exists
  - [ ] `event` module created and registered into the app and `SharedModule`
  - [ ] `Event` entity with fields: `title`, `slug` (unique), `description`,
    `icon`, `color` (`SimpleColorType`), `cover`, `status` (`StatusType`,
    scheduled → active → completed), `isOnline`, `meetingUrl` (nullable),
    `address` (nullable), `startsAt`, `endsAt`, `timezone`, `capacity`
    (nullable), `lang` (`LocaleType`), `owner`, `attendees`, `createdAt`, `updatedAt`
  - [ ] Full CRUD; user mutates only their own, admin any; non-owner/non-admin
    rejected; attending grants no update/delete
  - [ ] In-person references an `Address` (not free text); online has
    `meetingUrl`/no address, in-person has address/no `meetingUrl`
  - [ ] Address not owned by the event owner rejected; `endsAt` ≤ `startsAt`
    rejected; attendee beyond `capacity` rejected
  - [ ] If queues are used, attendee notifications run through a queue
  - [ ] Unneeded resources skipped and reported with a reason
  - [ ] `bun run fmt`, `bun run lint`, `bun run test` pass from the root