Appearance
Epic 17 — Messaging
Secure direct messaging between talents and producers. Rich text with media attachments. Referenced in spec section 2.4.
BE-MSG-001 — Messaging model and API
- [x] Implemented
Files:
- Create:
src/models/mongo/Message.ts— Mongoose schema (MongoDB for flexible message payloads) - Create:
src/models/mongo/Conversation.ts - Edit:
src/graphql/schema/index.ts— addConversation,Messagetypes + queries/mutations - Create:
src/graphql/resolvers/messaging.ts - Create:
src/services/messaging/index.ts
Schema (MongoDB):
ts
// Conversation
{ _id, participantIds: [userId], lastMessageAt, createdAt }
// Message
{ _id, conversationId, senderId, body: String, mediaUrls: [String],
readBy: [{ userId, readAt }], createdAt }GraphQL:
graphql
type Conversation { id: ID! participants: [User!]! lastMessage: Message messages(first: Int, after: String): MessageConnection! unreadCount: Int! }
type Message { id: ID! sender: User! body: String mediaUrls: [String!]! createdAt: DateTime! readByMe: Boolean! }
conversations: [Conversation!]!
conversation(id: ID!): Conversation
sendMessage(conversationId: ID, recipientId: ID, body: String!, mediaUrls: [String!]): Message!
markConversationRead(conversationId: ID!): Boolean!Notes:
sendMessagewithrecipientId(and noconversationId) creates a new conversation if one doesn't exist between the two users.- Outside a job context, sending a DM costs 50 CasTars (ties into BE-CASTARS-003
Direct Message Credit). - Paginate messages cursor-based (
first/after).
FE-MSG-001 — Messaging UI
- [x] Implemented
Real-time upgrade (post-MVP): the "future enhancement" WebSocket path is now built. The API runs a
graphql-wsserver on the same/graphqlendpoint (shared executable schema) with an in-memory PubSub. Subscriptions:messageAdded(conversationId),conversationUpdated,typing(conversationId); plus asetTypingmutation. The app uses agraphql-wsclient (auth via connectionParams) so threads stream live, the inbox + unread badge update onconversationUpdated, and a "typing…" indicator is shown. Polling remains only as a slow (60s) fallback. Deployment note: the reverse proxy must allow WebSocket upgrades on/graphql. To horizontally scale the API, swap the in-memory PubSub for a Redis-backed one (ioredis client already present) — topic names + call sites are unchanged.
Files:
- Create:
apps/app/src/pages/messaging/MessagesPage.tsx— conversation list - Create:
apps/app/src/pages/messaging/ConversationPage.tsx— message thread - Create:
apps/app/src/hooks/useMessaging.ts - Edit:
apps/app/src/App.tsx— add/messagesand/messages/:conversationIdroutes
Description:
- Conversation list: sorted by
lastMessageAtDESC. Each row: avatar, name, last message preview, unread count badge.EmptyStatewhen no conversations. - Thread view: chronological message bubbles (sender right, recipient left). Rich text input with emoji picker and
FileUploadfor media attachments. "Send" button. Scroll-to-bottom on new message. Marks as read on open. - New conversation: initiated from a talent/producer profile page via a "Message" button. Opens the thread directly.
- Poll every 15 s for new messages (WebSocket subscription in a future enhancement).