İletişim ve Mesajlaşma (Messaging System)
Eğitmen-öğrenci sohbeti, mesajlaşma tabloları, messages.tsx, görsel yükleme ve bildirimler.
Coursio’da öğrenciler, kayıtlı oldukları kursların eğitmenleriyle mesajlaşma üzerinden iletişim kurabilir; mesajlarda görsel paylaşımı ve bildirimler desteklenir. Bu sayfa conversations/messages tablo tasarımını, messages.tsx (öğrenci) ve instructor.messages.tsx (eğitmen) yapısını, api.chat-image-upload.ts ile resim yükleme akışını ve yeni mesaj / satış bildirimleri yönetimini açıklar.
Eğitmen-Öğrenci Sohbeti
Section titled “Eğitmen-Öğrenci Sohbeti”Mesajlaşma Tablosu Tasarımı
Section titled “Mesajlaşma Tablosu Tasarımı”Veritabanı: app/db/schema.ts
conversations (Konuşmalar)
| Alan | Tip | Açıklama |
|---|---|---|
id | uuid | Primary key. |
participant1_id | text (FK → users) | Genelde öğrenci. |
participant2_id | text (FK → users) | Genelde eğitmen. |
last_message | text | Son mesaj önizlemesi (düz metin). |
is_important | boolean | Önemli işaretleme (eğitmen tarafı). |
updated_at | timestamp | Son güncelleme. |
messages (Mesajlar)
| Alan | Tip | Açıklama |
|---|---|---|
id | uuid | Primary key. |
conversation_id | uuid (FK → conversations, CASCADE) | Hangi konuşma. |
sender_id | text (FK → users) | Gönderen kullanıcı. |
text | text | Metin içeriği (HTML veya düz metin). |
type | text | 'text', 'image', 'file'. |
media_url | text | Görsel/dosya URL’i (Bunny CDN). |
created_at | timestamp | Gönderim zamanı. |
is_delivered | boolean | İletildi. |
is_read | boolean | Okundu. |
Yetki: Öğrencinin bir eğitmene mesaj atabilmesi için canStudentMessageInstructor(db, studentId, instructorId) kontrolü yapılır: öğrenci o eğitmene ait en az bir kursta iade edilmemiş kayıtlı olmalı veya aktif abone olup eğitmenin aboneliğe açık bir kursuna erişiyor olmalı (app/lib/can-message-instructor.ts).
messages.tsx — Öğrenci Mesaj Sayfası
Section titled “messages.tsx — Öğrenci Mesaj Sayfası”Dosya: app/routes/messages.tsx
URL: /{lang}/messages (örn. /tr/messages).
Loader:
- Auth; yoksa login’e yönlendirme.
- Öğrenci ID =
session.user.id. Konuşmalar:participant1_id = studentId(öğrenci her zaman participant1) vecanStudentMessageInstructorgeçen satırlar; diğer taraf eğitmen (participant2). - Seçili konuşma: URL
?conv=...veya listedeki ilk konuşma. Seçili konuşmanın mesajları:messagestablosundanconversation_idile,created_atartan, limit 50; her mesajdaid,text,senderId,createdAt,type,mediaUrl,isDelivered,isRead. - Dönüş:
user,conversations,initialMessages,initialSelectedId,lang.
Sayfa yapısı:
- Sol panel: Arama, filtre (Tümü / Okunmamış / Önemli), konuşma listesi (avatar, ad, son mesaj önizlemesi, tarih, yıldız ile önemli işaretleme).
- Sağ panel: Seçili konuşma — üstte karşı taraf bilgisi; mesaj listesi (metin + varsa
type === 'image'içinmediaUrlile görsel; gönderene göre hizalama, teslim/okundu ikonu); altta metin girişi (ChatInput — Tiptap, HTML) ve görsel ekleme butonu (input file → api.chat-image-upload). - WebSocket:
wss://host/api/chat?userId=...&convId=...ile canlı bağlantı; mesaj gönderimi{ text, type: "text" }veya{ type: "image", mediaUrl, text: "" }; gelen mesajlarNEW_MESSAGEile listeye eklenir;MARK_READ,TOGGLE_IMPORTANTgibi olaylar gönderilir.
instructor.messages.tsx: Aynı arayüz; loader’da participant2_id = instructorId ile konuşmalar çekilir, diğer taraf öğrenci (participant1). Görsel yükleme ve WebSocket akışı öğrenci tarafıyla aynı mantıkta.
Görsel Paylaşımı: Mesajlarda Resim Yükleme
Section titled “Görsel Paylaşımı: Mesajlarda Resim Yükleme”Endpoint: POST /api/chat-image-upload
Dosya: app/routes/api.chat-image-upload.ts
Akış:
- FormData:
filealanında tek dosya; yoksa veyaFiledeğilse 400. - Tip kontrolü: Sadece
image/jpeg,image/png,image/gif,image/webpkabul edilir; aksi halde 400. - Bunny Storage: Ortam değişkenleri
BUNNY_STORAGE_ZONE,BUNNY_STORAGE_KEY,BUNNY_PULL_ZONE_URL; eksikse 500. - Dosya adı:
Date.now()+ sanitize edilmiş orijinal ad; path:chat-media/{safeName}. - Yükleme:
PUT https://storage.bunnycdn.com/{storageZone}/{uploadPath}— header:AccessKey: storageKey,Content-Type: file.type; body:Uint8Array(file.arrayBuffer()). - Yanıt: Başarılıysa
{ url: "{pullZoneUrl}/{uploadPath}" }(JSON); hata durumunda{ error: "..." }.
İstemci (messages.tsx / instructor.messages.tsx):
- Görsel ekleme butonu ile
<input type="file" accept="image/*" />tetiklenir. - Seçilen dosya
FormDatailePOST /api/chat-image-upload’a gönderilir. - Dönen
urlile WebSocket’e{ type: "image", mediaUrl: url, text: "" }gönderilir; sunucu tarafında mesaj kaydı oluşturulur (type: 'image',media_url: url). - Mesaj listesinde
msg.type === 'image' && msg.mediaUrlile resim gösterilir; tıklanınca büyük önizleme (Dialog) açılabilir.
Bildirimler
Section titled “Bildirimler”Yeni Mesaj Bildirimleri
Section titled “Yeni Mesaj Bildirimleri”Okunmamış mesaj sayısı (badge):
- Hook:
app/hooks/useChatNotifications.ts—useChatNotifications(userId). - GraphQL: İlk yükleme ve periyodik (örn. 45 sn) / sayfa odaklanınca
unreadMessagesCountsorgulanır; sayı artarsa bildirim tetiklenir. - WebSocket:
wss://host/api/chat/notifications?userId=...— gelen olaylar:NEW_MESSAGE: Okunmamış sayacı artırır; tarayıcı gizliyseNotificationAPI (izin varsa) ve toast ile “Yeni mesajınız var” gösterilir.UPDATE_BADGE: Okundu güncellemesi; sayaç düşer veyadata.countile güncellenir.
Navbar: Öğrenci/eğitmen menüsünde “Mesajlar” linki (/messages veya /instructor/messages); okunmamış sayı bu hook ile badge’de gösterilir. “Bildirimler” linki /{lang}/notifications (uygulamada öğrenci bildirim sayfası varsa oraya gider).
Satış Bildirimleri (E-posta)
Section titled “Satış Bildirimleri (E-posta)”Satış ve yeni öğrenci bildirimleri e-posta ile yönetilir; WebSocket/uygulama içi bildirim değildir.
Webhook (checkout.session.completed) içinde:
- Öğrenciye: Satın alma makbuzu —
sendPurchaseReceiptEmailWithInvoice(öğrenci e-posta, ad, makbuz kalemleri, fatura eki). - Eğitmene: Yeni öğrenci bilgisi —
sendNewStudentJoinedEmail(eğitmen e-posta, ad, kurs adı, öğrenci adı vb.).
Dosya: app/lib/email.ts — Resend (veya yapılandırılmış SMTP) ile gönderim.
Admin Bildirimleri
Section titled “Admin Bildirimleri”Admin paneli: app/routes/admin.tsx — “Son Hareketler” kutusu ve “Tüm Bildirimler” modalı.
- Veri:
getAdminNotificationCounts,getAdminRecentActivities,getAdminAllActivities(app/lib/db-queries.ts) — bekleyen kurs onayları, ödeme talepleri vb. sayılar ve son hareket listesi (ödeme talepleri + kurs onay talepleri). - API: “Tümünü gör” için
GET /api/admin/activities— en fazla 500 kayıt JSON döner. - Bu bildirimler yeni mesaj değil; admin işleri (onay bekleyen kurs, payout talepleri) içindir.
| Konu | Açıklama |
|---|---|
| Tablolar | conversations (participant1=öğrenci, participant2=eğitmen, last_message, is_important); messages (conversation_id, sender_id, text, type, media_url, is_delivered, is_read). |
| Yetki | canStudentMessageInstructor: öğrenci eğitmene ait kursta kayıtlı veya abonelik ile erişiyor olmalı. |
| messages.tsx | Öğrenci mesaj sayfası; konuşma listesi + seçili sohbet + WebSocket + ChatInput + görsel yükleme. |
| instructor.messages.tsx | Eğitmen tarafı aynı UI; participant2=eğitmen ile konuşmalar. |
| Görsel yükleme | POST /api/chat-image-upload → Bunny Storage (chat-media/); dönen URL WebSocket ile type: "image" mesajı olarak gönderilir. |
| Mesaj bildirimi | useChatNotifications: GraphQL unreadMessagesCount + WebSocket /api/chat/notifications (NEW_MESSAGE / UPDATE_BADGE); tarayıcı bildirimi + toast. |
| Satış bildirimi | E-posta: sendPurchaseReceiptEmailWithInvoice (öğrenci), sendNewStudentJoinedEmail (eğitmen) — webhook içinde. |
| Admin bildirim | Son hareketler + Tüm bildirimler modalı; ödeme talepleri ve kurs onayları. |
Canlı mesajlaşma için WebSocket sunucusu /api/chat (ve /api/chat/notifications) Durable Object veya ayrı bir WebSocket servisi ile implemente edilir; dokümantasyon bu sayfada özetlenen istemci ve tablo/API sözleşmelerine odaklanır.
İlgili Dosyalar
Section titled “İlgili Dosyalar”app/routes/messages.tsx— Öğrenci mesajlar sayfası.app/routes/instructor.messages.tsx— Eğitmen mesajlar sayfası.app/routes/api.chat-image-upload.ts— Sohbet görsel yükleme (Bunny Storage).app/lib/can-message-instructor.ts— Öğrenci–eğitmen mesaj yetkisi.app/lib/db-queries.ts— getAdminNotificationCounts, getAdminRecentActivities, getAdminAllActivities.app/routes/admin.tsx— Son hareketler ve Tüm bildirimler modalı.app/routes/api.admin.activities.ts— GET /api/admin/activities.