@studious-lms/server 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHAT_API_SPEC.md +579 -0
- package/dist/lib/pusher.d.ts +4 -0
- package/dist/lib/pusher.d.ts.map +1 -0
- package/dist/lib/pusher.js +9 -0
- package/dist/routers/_app.d.ts +496 -0
- package/dist/routers/_app.d.ts.map +1 -1
- package/dist/routers/_app.js +4 -0
- package/dist/routers/auth.d.ts.map +1 -1
- package/dist/routers/class.d.ts +10 -0
- package/dist/routers/class.d.ts.map +1 -1
- package/dist/routers/class.js +13 -0
- package/dist/routers/conversation.d.ts +134 -0
- package/dist/routers/conversation.d.ts.map +1 -0
- package/dist/routers/conversation.js +261 -0
- package/dist/routers/message.d.ts +108 -0
- package/dist/routers/message.d.ts.map +1 -0
- package/dist/routers/message.js +325 -0
- package/package.json +2 -1
- package/prisma/migrations/20250922165147_add_chat_models/migration.sql +68 -0
- package/prisma/migrations/20250922165314_add_chat_roles/migration.sql +5 -0
- package/prisma/migrations/20250922171242_refactor_notification_system/migration.sql +20 -0
- package/prisma/migrations/20250922172208_add_mentions_system/migration.sql +21 -0
- package/prisma/schema.prisma +68 -1
- package/src/lib/pusher.ts +11 -0
- package/src/routers/_app.ts +4 -0
- package/src/routers/auth.ts +1 -0
- package/src/routers/class.ts +13 -0
- package/src/routers/conversation.ts +285 -0
- package/src/routers/message.ts +365 -0
package/CHAT_API_SPEC.md
ADDED
|
@@ -0,0 +1,579 @@
|
|
|
1
|
+
# Chat API Specification - Studious LMS
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Real-time chat system with support for direct messages (DMs) and group conversations, featuring mentions and separate unread tracking.
|
|
6
|
+
|
|
7
|
+
### Stack
|
|
8
|
+
- **Backend**: tRPC + Prisma + PostgreSQL
|
|
9
|
+
- **Real-time**: Pusher
|
|
10
|
+
- **Authentication**: Bearer token in `Authorization` header
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 🔐 Authentication
|
|
15
|
+
|
|
16
|
+
All chat endpoints require authentication via Bearer token:
|
|
17
|
+
```typescript
|
|
18
|
+
headers: {
|
|
19
|
+
'Authorization': 'Bearer <your-session-token>'
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## 📝 Data Models
|
|
26
|
+
|
|
27
|
+
### Conversation Types
|
|
28
|
+
```typescript
|
|
29
|
+
type ConversationType = 'DM' | 'GROUP';
|
|
30
|
+
type ConversationRole = 'ADMIN' | 'MEMBER';
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### User Object
|
|
34
|
+
```typescript
|
|
35
|
+
interface User {
|
|
36
|
+
id: string;
|
|
37
|
+
username: string;
|
|
38
|
+
profile?: {
|
|
39
|
+
displayName?: string;
|
|
40
|
+
profilePicture?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Conversation Object
|
|
46
|
+
```typescript
|
|
47
|
+
interface Conversation {
|
|
48
|
+
id: string;
|
|
49
|
+
type: ConversationType;
|
|
50
|
+
name?: string; // Required for GROUP, null for DM
|
|
51
|
+
createdAt: Date;
|
|
52
|
+
updatedAt: Date;
|
|
53
|
+
members: ConversationMember[];
|
|
54
|
+
lastMessage?: Message;
|
|
55
|
+
unreadCount: number;
|
|
56
|
+
unreadMentionCount: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface ConversationMember {
|
|
60
|
+
id: string;
|
|
61
|
+
userId: string;
|
|
62
|
+
conversationId: string;
|
|
63
|
+
role: ConversationRole;
|
|
64
|
+
joinedAt: Date;
|
|
65
|
+
lastViewedAt?: Date;
|
|
66
|
+
lastViewedMentionAt?: Date;
|
|
67
|
+
user: User;
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Message Object
|
|
72
|
+
```typescript
|
|
73
|
+
interface Message {
|
|
74
|
+
id: string;
|
|
75
|
+
content: string;
|
|
76
|
+
senderId: string;
|
|
77
|
+
conversationId: string;
|
|
78
|
+
createdAt: Date;
|
|
79
|
+
sender: User;
|
|
80
|
+
mentions: Array<{ user: User }>;
|
|
81
|
+
mentionsMe: boolean; // True if current user is mentioned
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## 💬 Conversation Endpoints
|
|
88
|
+
|
|
89
|
+
### `conversation.list`
|
|
90
|
+
**Type**: Query
|
|
91
|
+
**Description**: Get all conversations for current user with unread counts
|
|
92
|
+
|
|
93
|
+
**Input**: None
|
|
94
|
+
|
|
95
|
+
**Output**:
|
|
96
|
+
```typescript
|
|
97
|
+
Array<{
|
|
98
|
+
id: string;
|
|
99
|
+
type: 'DM' | 'GROUP';
|
|
100
|
+
name?: string;
|
|
101
|
+
createdAt: Date;
|
|
102
|
+
updatedAt: Date;
|
|
103
|
+
members: ConversationMember[];
|
|
104
|
+
lastMessage?: Message;
|
|
105
|
+
unreadCount: number; // Regular unread messages
|
|
106
|
+
unreadMentionCount: number; // Unread mentions
|
|
107
|
+
}>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Example**:
|
|
111
|
+
```typescript
|
|
112
|
+
const conversations = await trpc.conversation.list.query();
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
### `conversation.create`
|
|
118
|
+
**Type**: Mutation
|
|
119
|
+
**Description**: Create a new conversation (DM or Group)
|
|
120
|
+
|
|
121
|
+
**Input**:
|
|
122
|
+
```typescript
|
|
123
|
+
{
|
|
124
|
+
type: 'DM' | 'GROUP';
|
|
125
|
+
name?: string; // Required for GROUP
|
|
126
|
+
memberIds: string[]; // User IDs to add (1 for DM, multiple for GROUP)
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Output**: `Conversation`
|
|
131
|
+
|
|
132
|
+
**Examples**:
|
|
133
|
+
```typescript
|
|
134
|
+
// Create DM
|
|
135
|
+
const dmConversation = await trpc.conversation.create.mutate({
|
|
136
|
+
type: 'DM',
|
|
137
|
+
memberIds: ['user-123']
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Create Group
|
|
141
|
+
const groupConversation = await trpc.conversation.create.mutate({
|
|
142
|
+
type: 'GROUP',
|
|
143
|
+
name: 'Project Team',
|
|
144
|
+
memberIds: ['user-123', 'user-456', 'user-789']
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Notes**:
|
|
149
|
+
- For DMs: Automatically detects and returns existing DM if it exists
|
|
150
|
+
- For Groups: Creator becomes ADMIN, others become MEMBER
|
|
151
|
+
- All memberIds must be valid user IDs
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
### `conversation.get`
|
|
156
|
+
**Type**: Query
|
|
157
|
+
**Description**: Get specific conversation details
|
|
158
|
+
|
|
159
|
+
**Input**:
|
|
160
|
+
```typescript
|
|
161
|
+
{
|
|
162
|
+
conversationId: string;
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Output**: `Conversation`
|
|
167
|
+
|
|
168
|
+
**Example**:
|
|
169
|
+
```typescript
|
|
170
|
+
const conversation = await trpc.conversation.get.query({
|
|
171
|
+
conversationId: 'conv-123'
|
|
172
|
+
});
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## 💬 Message Endpoints
|
|
178
|
+
|
|
179
|
+
### `message.list`
|
|
180
|
+
**Type**: Query
|
|
181
|
+
**Description**: Get paginated messages for a conversation (lazy loading)
|
|
182
|
+
|
|
183
|
+
**Input**:
|
|
184
|
+
```typescript
|
|
185
|
+
{
|
|
186
|
+
conversationId: string;
|
|
187
|
+
cursor?: string; // ISO date string for pagination
|
|
188
|
+
limit?: number; // Default: 50, Max: 100
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Output**:
|
|
193
|
+
```typescript
|
|
194
|
+
{
|
|
195
|
+
messages: Message[];
|
|
196
|
+
nextCursor?: string; // Use for next page
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Example**:
|
|
201
|
+
```typescript
|
|
202
|
+
// Get first page
|
|
203
|
+
const firstPage = await trpc.message.list.query({
|
|
204
|
+
conversationId: 'conv-123',
|
|
205
|
+
limit: 20
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Get next page
|
|
209
|
+
const nextPage = await trpc.message.list.query({
|
|
210
|
+
conversationId: 'conv-123',
|
|
211
|
+
cursor: firstPage.nextCursor,
|
|
212
|
+
limit: 20
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
**Notes**:
|
|
217
|
+
- Messages are returned in chronological order (oldest first)
|
|
218
|
+
- Use `cursor` for infinite scroll pagination
|
|
219
|
+
- Each message includes mention info and `mentionsMe` flag
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
### `message.send`
|
|
224
|
+
**Type**: Mutation
|
|
225
|
+
**Description**: Send a new message with optional mentions
|
|
226
|
+
|
|
227
|
+
**Input**:
|
|
228
|
+
```typescript
|
|
229
|
+
{
|
|
230
|
+
conversationId: string;
|
|
231
|
+
content: string; // 1-4000 characters
|
|
232
|
+
mentionedUserIds?: string[]; // Optional user IDs to mention
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Output**:
|
|
237
|
+
```typescript
|
|
238
|
+
{
|
|
239
|
+
id: string;
|
|
240
|
+
content: string;
|
|
241
|
+
senderId: string;
|
|
242
|
+
conversationId: string;
|
|
243
|
+
createdAt: Date;
|
|
244
|
+
sender: User;
|
|
245
|
+
mentionedUserIds: string[];
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Example**:
|
|
250
|
+
```typescript
|
|
251
|
+
// Regular message
|
|
252
|
+
const message = await trpc.message.send.mutate({
|
|
253
|
+
conversationId: 'conv-123',
|
|
254
|
+
content: 'Hello everyone!'
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Message with mentions
|
|
258
|
+
const mentionMessage = await trpc.message.send.mutate({
|
|
259
|
+
conversationId: 'conv-123',
|
|
260
|
+
content: 'Hey @john, can you review this?',
|
|
261
|
+
mentionedUserIds: ['user-john-id']
|
|
262
|
+
});
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
**Notes**:
|
|
266
|
+
- Mentioned users must be members of the conversation
|
|
267
|
+
- Broadcasts real-time event to all conversation members
|
|
268
|
+
- Updates conversation's `updatedAt` timestamp
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `message.markAsRead`
|
|
273
|
+
**Type**: Mutation
|
|
274
|
+
**Description**: Mark all messages in conversation as read
|
|
275
|
+
|
|
276
|
+
**Input**:
|
|
277
|
+
```typescript
|
|
278
|
+
{
|
|
279
|
+
conversationId: string;
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Output**:
|
|
284
|
+
```typescript
|
|
285
|
+
{
|
|
286
|
+
success: boolean;
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Example**:
|
|
291
|
+
```typescript
|
|
292
|
+
await trpc.message.markAsRead.mutate({
|
|
293
|
+
conversationId: 'conv-123'
|
|
294
|
+
});
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
**Notes**:
|
|
298
|
+
- Updates user's `lastViewedAt` timestamp for the conversation
|
|
299
|
+
- Affects `unreadCount` but NOT `unreadMentionCount`
|
|
300
|
+
- Broadcasts `conversation-viewed` event
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
### `message.markMentionsAsRead`
|
|
305
|
+
**Type**: Mutation
|
|
306
|
+
**Description**: Mark all mentions in conversation as read (separate from regular messages)
|
|
307
|
+
|
|
308
|
+
**Input**:
|
|
309
|
+
```typescript
|
|
310
|
+
{
|
|
311
|
+
conversationId: string;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
**Output**:
|
|
316
|
+
```typescript
|
|
317
|
+
{
|
|
318
|
+
success: boolean;
|
|
319
|
+
}
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
**Example**:
|
|
323
|
+
```typescript
|
|
324
|
+
await trpc.message.markMentionsAsRead.mutate({
|
|
325
|
+
conversationId: 'conv-123'
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
**Notes**:
|
|
330
|
+
- Updates user's `lastViewedMentionAt` timestamp
|
|
331
|
+
- Affects `unreadMentionCount` but NOT `unreadCount`
|
|
332
|
+
- Broadcasts `mentions-viewed` event
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
### `message.getUnreadCount`
|
|
337
|
+
**Type**: Query
|
|
338
|
+
**Description**: Get unread counts for a conversation
|
|
339
|
+
|
|
340
|
+
**Input**:
|
|
341
|
+
```typescript
|
|
342
|
+
{
|
|
343
|
+
conversationId: string;
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**Output**:
|
|
348
|
+
```typescript
|
|
349
|
+
{
|
|
350
|
+
unreadCount: number; // Regular unread messages
|
|
351
|
+
unreadMentionCount: number; // Unread mentions
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
**Example**:
|
|
356
|
+
```typescript
|
|
357
|
+
const counts = await trpc.message.getUnreadCount.query({
|
|
358
|
+
conversationId: 'conv-123'
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Use for UI badges
|
|
362
|
+
if (counts.unreadMentionCount > 0) {
|
|
363
|
+
// Show red mention badge
|
|
364
|
+
} else if (counts.unreadCount > 0) {
|
|
365
|
+
// Show regular unread badge
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## 🔴 Real-time Events (Pusher)
|
|
372
|
+
|
|
373
|
+
### Channel Pattern
|
|
374
|
+
Subscribe to: `conversation-{conversationId}`
|
|
375
|
+
|
|
376
|
+
### Events
|
|
377
|
+
|
|
378
|
+
#### `new-message`
|
|
379
|
+
**Triggered**: When someone sends a message
|
|
380
|
+
**Payload**:
|
|
381
|
+
```typescript
|
|
382
|
+
{
|
|
383
|
+
id: string;
|
|
384
|
+
content: string;
|
|
385
|
+
senderId: string;
|
|
386
|
+
conversationId: string;
|
|
387
|
+
createdAt: Date;
|
|
388
|
+
sender: User;
|
|
389
|
+
mentionedUserIds: string[];
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
#### `conversation-viewed`
|
|
394
|
+
**Triggered**: When someone marks messages as read
|
|
395
|
+
**Payload**:
|
|
396
|
+
```typescript
|
|
397
|
+
{
|
|
398
|
+
userId: string;
|
|
399
|
+
viewedAt: Date;
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
#### `mentions-viewed`
|
|
404
|
+
**Triggered**: When someone marks mentions as read
|
|
405
|
+
**Payload**:
|
|
406
|
+
```typescript
|
|
407
|
+
{
|
|
408
|
+
userId: string;
|
|
409
|
+
viewedAt: Date;
|
|
410
|
+
}
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
### Frontend Integration Example
|
|
414
|
+
```typescript
|
|
415
|
+
import Pusher from 'pusher-js';
|
|
416
|
+
|
|
417
|
+
const pusher = new Pusher('your-pusher-key', {
|
|
418
|
+
cluster: 'your-cluster'
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
// Subscribe to conversation
|
|
422
|
+
const channel = pusher.subscribe(`conversation-${conversationId}`);
|
|
423
|
+
|
|
424
|
+
// Listen for new messages
|
|
425
|
+
channel.bind('new-message', (data) => {
|
|
426
|
+
// Add message to UI
|
|
427
|
+
addMessageToUI(data);
|
|
428
|
+
|
|
429
|
+
// Check if user is mentioned
|
|
430
|
+
if (data.mentionedUserIds.includes(currentUserId)) {
|
|
431
|
+
// Show mention notification
|
|
432
|
+
showMentionNotification(data);
|
|
433
|
+
}
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
// Listen for read receipts
|
|
437
|
+
channel.bind('conversation-viewed', (data) => {
|
|
438
|
+
// Update read indicators
|
|
439
|
+
updateReadIndicators(data.userId, data.viewedAt);
|
|
440
|
+
});
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## 🎨 UI/UX Guidelines
|
|
446
|
+
|
|
447
|
+
### Unread Indicators
|
|
448
|
+
```typescript
|
|
449
|
+
// Conversation list item
|
|
450
|
+
function ConversationItem({ conversation }) {
|
|
451
|
+
const { unreadCount, unreadMentionCount } = conversation;
|
|
452
|
+
|
|
453
|
+
return (
|
|
454
|
+
<div className="conversation-item">
|
|
455
|
+
<div className="conversation-info">
|
|
456
|
+
{/* Conversation details */}
|
|
457
|
+
</div>
|
|
458
|
+
|
|
459
|
+
<div className="badges">
|
|
460
|
+
{unreadMentionCount > 0 && (
|
|
461
|
+
<Badge variant="mention" count={unreadMentionCount} />
|
|
462
|
+
)}
|
|
463
|
+
{unreadCount > 0 && (
|
|
464
|
+
<Badge variant="regular" count={unreadCount} />
|
|
465
|
+
)}
|
|
466
|
+
</div>
|
|
467
|
+
</div>
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
### Message Display
|
|
473
|
+
```typescript
|
|
474
|
+
function MessageItem({ message, currentUserId }) {
|
|
475
|
+
const isMentioned = message.mentionsMe;
|
|
476
|
+
const isOwnMessage = message.senderId === currentUserId;
|
|
477
|
+
|
|
478
|
+
return (
|
|
479
|
+
<div className={`message ${isMentioned ? 'mentioned' : ''} ${isOwnMessage ? 'own' : ''}`}>
|
|
480
|
+
<div className="message-header">
|
|
481
|
+
<span className="sender">{message.sender.profile?.displayName || message.sender.username}</span>
|
|
482
|
+
<span className="timestamp">{formatTime(message.createdAt)}</span>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
<div className="message-content">
|
|
486
|
+
{renderMessageWithMentions(message.content, message.mentions)}
|
|
487
|
+
</div>
|
|
488
|
+
</div>
|
|
489
|
+
);
|
|
490
|
+
}
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Mention Input
|
|
494
|
+
```typescript
|
|
495
|
+
// When user types @username, suggest users from conversation members
|
|
496
|
+
function MessageInput({ conversationId, members }) {
|
|
497
|
+
const [content, setContent] = useState('');
|
|
498
|
+
const [mentionedUsers, setMentionedUsers] = useState<string[]>([]);
|
|
499
|
+
|
|
500
|
+
const handleSend = async () => {
|
|
501
|
+
await trpc.message.send.mutate({
|
|
502
|
+
conversationId,
|
|
503
|
+
content,
|
|
504
|
+
mentionedUserIds: mentionedUsers
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
setContent('');
|
|
508
|
+
setMentionedUsers([]);
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Implement @mention autocomplete logic
|
|
512
|
+
}
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## ⚠️ Error Handling
|
|
518
|
+
|
|
519
|
+
### Common Error Codes
|
|
520
|
+
- `UNAUTHORIZED`: Invalid or missing authentication
|
|
521
|
+
- `FORBIDDEN`: Not a member of the conversation
|
|
522
|
+
- `NOT_FOUND`: Conversation doesn't exist
|
|
523
|
+
- `BAD_REQUEST`: Invalid input (e.g., mentioned user not in conversation)
|
|
524
|
+
|
|
525
|
+
### Error Response Format
|
|
526
|
+
```typescript
|
|
527
|
+
{
|
|
528
|
+
error: {
|
|
529
|
+
code: string;
|
|
530
|
+
message: string;
|
|
531
|
+
data?: any;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## 🚀 Implementation Checklist
|
|
539
|
+
|
|
540
|
+
### Basic Chat
|
|
541
|
+
- [ ] Display conversation list with unread counts
|
|
542
|
+
- [ ] Create DM conversations
|
|
543
|
+
- [ ] Send and receive messages
|
|
544
|
+
- [ ] Real-time message updates
|
|
545
|
+
- [ ] Mark conversations as read
|
|
546
|
+
|
|
547
|
+
### Advanced Features
|
|
548
|
+
- [ ] Create group conversations
|
|
549
|
+
- [ ] @mention autocomplete
|
|
550
|
+
- [ ] Separate mention/regular unread tracking
|
|
551
|
+
- [ ] Mention notifications
|
|
552
|
+
- [ ] Message pagination/infinite scroll
|
|
553
|
+
- [ ] Typing indicators (optional)
|
|
554
|
+
- [ ] Message search (optional)
|
|
555
|
+
|
|
556
|
+
### UI Components Needed
|
|
557
|
+
- [ ] ConversationList
|
|
558
|
+
- [ ] ConversationItem
|
|
559
|
+
- [ ] MessageList
|
|
560
|
+
- [ ] MessageItem
|
|
561
|
+
- [ ] MessageInput
|
|
562
|
+
- [ ] MentionAutocomplete
|
|
563
|
+
- [ ] UnreadBadge
|
|
564
|
+
- [ ] UserAvatar
|
|
565
|
+
|
|
566
|
+
---
|
|
567
|
+
|
|
568
|
+
## 📱 Mobile Considerations
|
|
569
|
+
|
|
570
|
+
- Use appropriate notification permissions
|
|
571
|
+
- Handle app backgrounding/foregrounding
|
|
572
|
+
- Implement proper connection management for Pusher
|
|
573
|
+
- Consider offline message queuing
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
**Generated**: September 22, 2025
|
|
578
|
+
**Version**: 1.0
|
|
579
|
+
**Backend Version**: Compatible with Studious LMS Server v1.1.8+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pusher.d.ts","sourceRoot":"","sources":["../../src/lib/pusher.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,QAAA,MAAM,MAAM,QAMV,CAAC;AAEH,OAAO,EAAE,MAAM,EAAE,CAAC"}
|