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