title: "Scaffold the chat module and its resources"
context: |
Build a `chat` domain: the persisted record of a user's AI conversations, a
three-level hierarchy — a conversation groups driver runs (histories), each
history holds ordered messages. The `@ooneex/ai` package already defines the
base shape: `ChatConversationEntity`, `ChatHistoryEntity`, `ChatMessageEntity`
(plus `flattenContent`) — align to those. A conversation is private: a user
sees/edits only their own; an admin manages any. Auth is owner-or-admin on
every read/list/mutation; histories and messages have no independent owner —
they inherit the conversation's. If `modules/chat/` already exists, this work
is void — do not run.
goal: |
Create the `chat` module + needed resources, wired owner-or-admin on every op.
## Notes
- If `modules/chat/` exists, STOP and report. Else `/module:create` chat, then
build each resource via its `*:create` skill (`--module=chat`), respecting
controllers → services → repositories → entities, registering all.
- Model the three entities against `@ooneex/ai`: `ChatConversation` /
`ChatHistory` / `ChatMessage` mirror their `*Entity`; reuse `MessageType` and
`flattenContent`.
- Judge each resource; create the justified, skip the rest with a reason.
Defaults: entity + repository always (all three); service + controller per use
case (start conversation, append history, append messages, read/list own with
nested data, delete); permission always (owner-or-admin on reads/lists/writes
so no user loads another's data, reuse the permission service); migration if
the project uses migrations (mirror package migrations — index `chat_messages`
on `(chat_history_id, created_at)`); event only if a real subscriber;
translation/storage/seed/queue/workflow skip (user data, multimodal parts
referenced not stored, append-only).
- Persist content faithfully: `content` always holds the flattened plain text
(via `flattenContent`); `parts` preserves the original multimodal
`ContentPart[]` when not plain text.
- Provenance: `ChatHistory.model` is the run's driver model; an assistant
`ChatMessage.model` is its producing model; a user message has `null`.
- Honor the package cascade/soft-delete: deleting a conversation cascades to
histories and messages; all three soft-delete via `deletedAt`.
- Throw typed exceptions (e.g. `ChatConversationNotFoundException`), never
return null; another user's data must return not-found.
### Data Model
- `ChatConversation.owner` ↔ `User.chatConversations` — ManyToOne / OneToMany
- `ChatConversation.histories` ↔ `ChatHistory.conversation` — OneToMany /
ManyToOne, cascade, `onDelete: CASCADE`
- `ChatHistory.messages` ↔ `ChatMessage.history` — OneToMany / ManyToOne,
cascade, `onDelete: CASCADE`
- Histories and messages have no `owner`; they inherit it from the conversation.
dod: |
- [ ] Aborts with a report if `modules/chat/` exists
- [ ] `chat` module created and registered into the app and `SharedModule`
- [ ] `ChatConversation` (aligned to `ChatConversationEntity`): `title`
(nullable), `owner`, `histories`, `createdAt`, `updatedAt`, `deletedAt` (nullable)
- [ ] `ChatHistory` (aligned to `ChatHistoryEntity`): `conversation`
(`onDelete: CASCADE`), `model`, `systemPrompts` (jsonb, nullable), `metadata`
(jsonb, nullable), `messages`, `createdAt`, `updatedAt`, `deletedAt` (nullable)
- [ ] `ChatMessage` (aligned to `ChatMessageEntity`): `history`
(`onDelete: CASCADE`), `role` (`MessageType["role"]`), `content` (flattened
text), `parts` (multimodal `ContentPart[]`, jsonb, nullable), `model`
(nullable for user messages), `createdAt`, `updatedAt`, `deletedAt` (nullable)
- [ ] Conversations/histories/messages can be created, read, listed, deleted;
user accesses only their own, admin any; another user's data returns not-found
- [ ] `content` holds flattened text, `parts` preserves multimodal when present;
assistant message records its `model`, user message none
- [ ] Deleting a conversation cascades to histories and messages; all three
soft-delete via `deletedAt`
- [ ] If migrations are managed, `chat_messages` indexed on `(chat_history_id, created_at)`
- [ ] Unneeded resources skipped and reported with a reason
- [ ] `bun run fmt`, `bun run lint`, `bun run test` pass from the root