Lewati ke konten utama

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​

KomponenTeknologi
Backend WebSocketLaravel Reverb
Frontend StreamFlutter + web_socket_channel
Message StorageMySQL
Broadcast DriverReverb

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​

TipeDeskripsiLifetime
Booking ChatChat antara siswa dan tutor selama proses bookingHingga sesi selesai
Meeting ChatChat selama sesi berlangsung, bisa diikuti guruSelama on_going
Group ChatJika 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​

FiturImplementasi
RealtimeLaravel Reverb + WebSocket
Typing IndicatorBroadcast typing event
Read ReceiptUpdate is_read via API
Reply Messagereply_to_id kolom relasi
Image UploadLaravel Storage + presigned URL
PDF AttachmentLaravel Storage, max 5MB
TimestampDisimpan 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​

TipeKebijakan
Booking ChatDiarsipkan 30 hari setelah sesi selesai
Meeting ChatDipertahankan sebagai dokumentasi sesi
AttachmentDihapus setelah 90 hari