BAB 47. CHAT ARCHITECTURE
Tujuan
Chat digunakan untuk koordinasi sebelum, saat, dan setelah sesi belajar antara siswa dan tutor, serta memungkinkan guru bergabung untuk memberikan bimbingan tambahan.
Stack Teknologi
| Komponen | Teknologi |
|---|---|
| Backend WebSocket | Laravel Reverb |
| Frontend Stream | Flutter + web_socket_channel |
| Message Storage | MySQL |
| Broadcast Driver | Reverb |
Arsitektur Chat
flowchart LR
FA["📱 Flutter\n(Siswa)"] -->|"WebSocket"| RV["⚡ Laravel Reverb"]
FB["📱 Flutter\n(Tutor)"] -->|"WebSocket"| RV
FC["📱 Flutter\n(Guru)"]] -->|"WebSocket"| RV
RV <-->|"Private Channel\nbooking.{id}"| LV["🟥 Laravel\nBroadcast"]
LV --> DB[("💾 MySQL")]
FA -->|"REST POST /messages"| API["🌐 Laravel API"]
API --> DB
DB -->|"MessageSent Event"| LV
style RV fill:#dc2626,color:#fff
style LV fill:#b45309,color:#fff
Tipe Chat Room
| Tipe | Deskripsi | Lifetime |
|---|---|---|
| Booking Chat | Chat antara siswa dan tutor selama proses booking | Hingga sesi selesai |
| Meeting Chat | Chat selama sesi berlangsung, bisa diikuti guru | Selama on_going |
| Group Chat | Jika ada lebih dari 1 tutor (kelompok belajar) | Hingga sesi selesai |
Model Data Chat
chat_rooms
├── id
├── booking_id FK → bookings
├── type enum: booking, meeting, group
└── created_at
chat_messages
├── id
├── room_id FK → chat_rooms
├── sender_id FK → users
├── type enum: text, image, file, reply
├── content text
├── attachment_path varchar (nullable)
├── reply_to_id FK → chat_messages (nullable)
├── is_read boolean
└── sent_at timestamp
Fitur Chat
| Fitur | Implementasi |
|---|---|
| Realtime | Laravel Reverb + WebSocket |
| Typing Indicator | Broadcast typing event |
| Read Receipt | Update is_read via API |
| Reply Message | reply_to_id kolom relasi |
| Image Upload | Laravel Storage + presigned URL |
| PDF Attachment | Laravel Storage, max 5MB |
| Timestamp | Disimpan di sent_at |
Implementasi Flutter
// features/chat/data/datasource/chat_realtime_datasource.dart
class ChatRealtimeDatasource {
late final IOWebSocketChannel _channel;
void connect(int roomId, String token) {
_channel = IOWebSocketChannel.connect(
Uri.parse('wss://api.temubelajar.id/app/key'),
);
// Subscribe to private channel
_channel.sink.add(jsonEncode({
'event': 'pusher:subscribe',
'data': {'channel': 'private-room.$roomId', 'auth': token},
}));
}
Stream<ChatMessage> get messages => _channel.stream
.map((data) => ChatMessage.fromJson(jsonDecode(data)));
void sendTyping(int roomId) {
_channel.sink.add(jsonEncode({
'event': 'client-typing',
'channel': 'private-room.$roomId',
}));
}
void dispose() => _channel.sink.close();
}
Retensi Data
| Tipe | Kebijakan |
|---|---|
| Booking Chat | Diarsipkan 30 hari setelah sesi selesai |
| Meeting Chat | Dipertahankan sebagai dokumentasi sesi |
| Attachment | Dihapus setelah 90 hari |