Skip to content

Öğrenme Deneyimi (Student Experience)

Ders izleme paneli (learn.$slug), izlenme telemetrisi (api.video-telemetry), sertifika sistemi (PDF indirme).

Achidemy’de öğrenciler kursa kayıt olduktan sonra ders izleme paneli ile içeriği takip eder, izlenme süresi (heartbeat) kaydedilir ve kurs bitiminde sertifika indirebilir. Bu sayfa Course Player (learn.$slug.tsx) yapısını, api.video-telemetry.ts ile saniye bazlı telemetriyi ve api.certificate.$id.download.ts ile dinamik PDF sertifika üretimini açıklar.

Dosya: app/routes/learn.$slug.tsx

URL: /{lang}/learn/{courseSlug} (örn. /tr/learn/react-egitimi-abc12).

  1. Auth: Oturum kontrolü; yoksa 401.
  2. Kurs: getCourseBySlug(db, params.slug) ile kurs bulunur; yoksa 404.
  3. Erişim: checkEnrollment(db, userId, course.id) — kayıt yoksa 403.
  4. Müfredat ve ilerleme (paralel):
    • getCourseCurriculumWithResources(db, course.id) → bölümler, dersler, quiz’ler, kod egzersizleri, kaynaklar.
    • getCompletedLessonIds(db, userId, course.id) → tamamlanan ders ID’leri (user_lessons_progress.completed = true).
    • getCompletedQuizIds, getCompletedExerciseIds → tamamlanan quiz ve egzersiz ID’leri.
  5. Video URL’leri: Kayıtlı kullanıcı (veya önizleme dersi) için her video dersine generateSignedVideoUrl(lesson.bunnyVideoId, libraryId, securityKey) ile imzalı URL eklenir (lesson.signedVideoUrl).

Loader çıktısı: course, curriculum, completedLessons, completedQuizzes, completedExercises, bunnyLibraryId.

  • Header: Geri (my-courses), kurs başlığı, müfredat menü toggle (mobil), “Sertifika kazan” metni.
  • Ana alan:
    • Video ders: Bunny embed iframe (signedVideoUrl veya fallback embed URL), oynatma/duraklatma, süre takibi. Video bittiğinde otomatik tamamlama (toggleComplete) ve isteğe bağlı sonraki derse geçiş (geri sayım).
    • Quiz: QuizPlayer — sorular, cevaplar, tamamlanınca revalidate ve sonraki içeriğe geçiş.
    • Kod egzersizi: CodingWorkspace — dil, başlangıç kodu, test, tamamlanınca onComplete → revalidate. Mobil cihazlarda (ekran genişliği < 1024px) editör sadece okunabilir ve çalıştır butonları devre dışıdır; üstte “masaüstünden devam edin” uyarı banner’ı gösterilir. Detay: Mobil UX ve Erişilebilirlik.
  • Sekmeler (içerik / Q&A / notlar): İçerik kaynakları, Soru-Cevap (lazy CourseQA), Notlar (lazy CourseNotes).
  • Yan panel (sidebar): Bölümler (Accordion), her bölümde ders / quiz / egzersiz listesi; tamamlananlar checkbox ile işaretli; tıklanınca activeLesson / activeQuiz / activeExercise değişir. İlerleme özeti: “X / Y tamamlandı”.
  • Ders tamamlama: Kullanıcı checkbox ile “tamamladım” işaretleyebilir veya video sonuna gelince otomatik tamamlanır. Her iki durumda da toggleLessonCompletion(lessonId, courseId, completed: true) GraphQL mutation’ı çağrılır; user_lessons_progress.completed = true yapılır. Tamamlama sonrası izlenme süresi varsa stopWatchingTracking(true) veya reportWatchedTime(0, true) ile son kez raporlanır.
  • İzlenme süresi (learn sayfası): useVideoTelemetry ile 15 saniyelik bloklar halinde POST /api/video-telemetry endpoint’ine gönderilir. Sunucu 200 OK dönmeden client tamponundan düşmez (kayıpsız tampon). Sekme değişse bile video oynuyorsa heartbeat devam eder; gerçek duraklama “video time ilerlemiyor” durumundan anlaşılır.

Endpoint: POST /api/video-telemetry
Dosya: app/routes/api.video-telemetry.ts

Amaç: Video izlerken saniye bazında izlenme telemetrisi (heartbeat) kaydetmek. Sistem, DB’ye 15 saniyelik bloklar halinde yazacak şekilde tasarlanmıştır.

Beklenen gövde (JSON):

  • courseId (string, UUID)
  • lessonId (string, UUID)
  • durationSeconds (number, pozitif tam sayı, pratikte 15)
  • timestamp (number, optional)

İşlemler (Neon HTTP driver uyumlu, transaction’sız):

  1. watch_time_logs: Her heartbeat insert edilir (debug/analitik + payout için ham kaynak).
  2. daily_activities: Session’dan userId alınır; userId + bugünün tarihi (YYYY-MM-DD, UTC) için totalWatchedSeconds artırılır (streak/banner kaynağı).
  3. subscription_watch_logs: Kullanıcının aboneliği aktif ve kurs aboneliğe açıksa userId, instructorId, tarih için durationSeconds artırılır (abonelik havuzu payı).
  4. streak: daily_activities bugünkü toplamı 20 dakika (1200 sn) eşiğini geçtiğinde updateStreak tetiklenir.

Not: Ders “tamamlandı” bilgisi (completed: true) bu endpoint’te güncellenmez; tamamlama GraphQL toggleLessonCompletion ile yapılır. Bu API sadece izlenen süre birikimini günceller.

Enterprise Anti‑Cheat (Payout Koruması)

Section titled “Enterprise Anti‑Cheat (Payout Koruması)”

Telemetri endpoint’i, payout (eğitmen gelir dağıtımı) tarafını korumak için ek kurallar uygular. Bu kurallar streak/motivasyon kayıtlarını durdurmaz; sadece “para havuzuna dahil edilen” izlenmeleri sınırlar.

  • Self‑Watch Ban (Kendi kursunu izleme yasağı):

    • Eğer course.instructorId === userId ise payout’a yazılmaz.
    • Sonuç: watch_time_logs ve subscription_watch_logs için kayıt atlanır, ancak daily_activities yine artar (UX/streak testi için).
  • Human Daily Cap (Günlük insani sınır):

    • Kullanıcı başına günlük maksimum paid süre: 8 saat = 28.800 sn.
    • Bu limite ulaşıldıktan sonra gelen heartbeat’ler payout’a dahil edilmez (log atlanır).
  • Lesson Multiplier Cap (Ders bazlı 3x tavan):

    • Bir öğrencinin aynı lessonId için ay içinde “paid” sayılacak toplam süresi, dersin süresinin en fazla 3 katı olabilir:
      • Formül: maxPaidSeconds = lessons.duration * 3
    • Limit aşılıyorsa bu heartbeat payout’a dahil edilmez (log atlanır).

Not: Client tarafı heartbeat blok boyutu 15 sn olacak şekilde tasarlanmıştır; payout tarafında “kısmi kırpma” yapılmaz (ya 15 sayılır, ya 0).

  • useVideoTelemetry (app/hooks/useVideoTelemetry.ts): Oynatma durumunu (isPlaying) learn.$slug.tsx içinden alır; client tarafında saniyeleri tamponlar; 15 saniye tamamlandıkça /api/video-telemetry’ye gönderir; 200 OK olmadan tampondan düşmez.
  • learn.$slug.tsx: isVideoPlaying durumu “video time ilerliyor mu?” ile türetilir (autoplay / event kaçaklarını tolere eder).
  • getCompletedLessonIds (app/lib/db-queries.ts): user_lessons_progress tablosunda userId, courseId ve completed = true olan kayıtların lessonId listesini döner. Sidebar’daki tikler ve “X / Y tamamlandı” bu listeye göre hesaplanır.

Sertifika, tüm dersler tamamlandığında oluşturulur. GraphQL toggleLessonCompletion mutation’ında, bir ders completed: true yapıldıktan sonra:

  1. Kurstaki toplam ders sayısı (sadece lesson, quiz/egzersiz sayılmıyor) hesaplanır.
  2. Kullanıcının bu kursta completed = true olan ders sayısı hesaplanır.
  3. Bu iki sayı eşitse ve o kullanıcı+kurs için certificates tablosunda kayıt yoksa, yeni sertifika kaydı eklenir: userId, courseId, certificateCode (benzersiz), fullName, courseTitle, instructorName, issueDate.

Böylece kurs bitiminde sertifika otomatik oluşur.

URL: GET /api/certificates/:id/download
Dosya: app/routes/api.certificate.$id.download.ts

Akış:

  1. Auth: Session kontrolü; giriş yoksa 401.
  2. Yetki: Sertifika id ve userId ile sorgulanır; kayıt kullanıcıya ait değilse 404.
  3. Kurs süresi: İlgili kurstaki tüm derslerin lessons.duration toplamı (saniye) alınır.
  4. Doğrulama linki: origin + "/verify/" + cert.certificateCode (örn. https://site.com/verify/CRT-xyz).
  5. Referans no: Kategori kodu (categoryToCode) + sertifika kodunun son 6 karakteri (örn. ABC-123456).
  6. QR kod: QRCode.toDataURL(verifyUrl) ile Base64 Data URL üretilir (PDF’e gömülecek).
  7. PDF: CertificateTemplate React bileşeni (@react-pdf/renderer) ile renderToStream(pdfElement) çağrılır; data olarak sertifika alanları, durationSeconds, referenceNumber, qrDataUrl verilir.
  8. Yanıt: Content-Type: application/pdf, Content-Disposition: attachment; filename="certificate-{code}.pdf", Cache-Control: no-store.

Dosya: app/components/CertificateTemplate.tsx

  • @react-pdf/renderer kullanır: Document, Page, Text, View, StyleSheet, Image.
  • Tek sayfa: arka plan, dekoratif çember/yıldız, başlık (“Sertifika” / “Certificate”), fullName, courseTitle, “tamamladı” metni, süre (dakika/saat), referans numarası, issueDate, QR kod (Image src={data.qrDataUrl}).
  • Görsel stil: Beyaz içerik alanı, mavi tonlarında arka plan; PDF çıktısı yazdırma ve paylaşım için uygundur.
  • Public URL: /verify/:code — giriş gerekmez; certificateCode ile sertifika sorgulanır, varsa detay sayfası (veya doğrulama sonucu) gösterilir. Sertifika indirme ise yine /api/certificates/:id/download ile kullanıcıya özeldir.
  • daily_activities: Günlük izlenen süre (saniye) kaydedilir; POST /api/video-telemetry ile güncellenir.
  • user: currentStreak, longestStreak, lastActivityDate, totalLearningMinutes, streakFreezeCount — streak hesaplaması ve donma hakkı (1 gün kaçırma) için kullanılır.
  • Öğrenci her gün belirli süre izleme yaparak seriyi sürdürür; streak bilgisi öğrenme sayfası veya hesap alanında gösterilebilir.

Sepet, İstek Listesi, Araçlar ve Paketler

Section titled “Sepet, İstek Listesi, Araçlar ve Paketler”
  • Sepet: cart.tsx — giriş yapan kullanıcıda cart_items tablosu kullanılır; misafir kullanıcıda sepet guest_cart çerezi ile tutulur ({ id, type }). Giriş sonrası /:lang/cart loader misafir sepetini DB ile merge eder ve çerezi temizler; ödemeye geçiş için misafirler /:lang/login?intent=checkout akışını kullanır.
  • İstek listesi: my-courses.wishlist.tsxwishlists tablosu; öğrenci kursu listeye ekleyip sonra satın alabilir.
  • Öğrenci Araçları (Tools): my-courses.tools.tsx — Öğrencinin ilerleme ve motivasyonunu artırmaya yönelik yardımcı araçların hub sayfası. Kart yapısı ile (1) Akıllı Çalışma Planlayıcı, (2) Odak Merkezi (Pomodoro), (3) AI Ders Özeti Çıkarıcı, (4) Çalışma Serisi Haritası gibi araçlara giriş sağlar.
  • Akıllı Çalışma Planlayıcı: my-courses.tools.calendar.tsx — Öğrencinin kayıtlı olduğu kursları enrollments + courses üzerinden loader’da çeker, seçtiği kurs/gün/saat/süre bilgisine göre Google Calendar linki ve Apple/Outlook için .ics dosyası üretir. Haftalık tekrarlayan etkinlik (RRULE:FREQ=WEEKLY;BYDAY=…) mantığıyla çalışır, tüm metinler studentTools.* i18n anahtarları ile TR/EN/ES/DE/FR/JA locale’lerine göre çevrilir.
  • Paketler (vitrin): bundle.$slug.tsx — Kurs paketi vitrin sayfası; kurs detay sayfasıyla aynı grid düzeni, promo video sağ fiyat kartının içinde, bölgesel paket fiyatı (getBundleRegionalPrice), indirim tarih aralığı kontrolü, createBundleCheckoutSession ile ödeme. Sepete paket ekleme ve kurs sayfasındaki “sık birlikte alınan paket” upsell’i için detay: Kurs ve Paket Vitrin Sayfaları. Satın alma sonrası paketteki her kurs için ayrı enrollment oluşturulur (webhook / checkout akışı).
  • Sertifikalar sayfası: my-courses.certifications.tsx — Öğrencinin tamamladığı kurslara ait sertifikalar listelenir; PDF indirme /api/certificate/:id/download ile yapılır.
  • Arşivlenen kurslar: my-courses.archived.tsxstatus = 'archived' veya kullanıcı tarafından arşive alınan kurslar (varsa) listelenir.
KonuAçıklama
Course Playerlearn.$slug.tsx: loader (enrollment, curriculum, completed*, signedVideoUrl); header + ana alan (video/quiz/egzersiz) + sekmeler + sidebar (bölüm/ders listesi, ilerleme).
İzlenme TelemetrisiPOST /api/video-telemetry: 15 saniyelik heartbeat blokları; watch_time_logs (insert), daily_activities (upsert), subscription_watch_logs (abonelik ise upsert), streak tetikleme.
Tamamlamauser_lessons_progress.completed = true → getCompletedLessonIds; tüm dersler tamamlanınca sertifika kaydı oluşturulur.
Streakdaily_activities + user.currentStreak/longestStreak; günlük izleme ile seri sürdürülür.
Sepet / Wishlistcart_items, wishlists; checkout ve my-courses.wishlist ile kullanılır.
Öğrenci Araçlarımy-courses.tools.tsx hub + my-courses.tools.calendar.tsx Akıllı Çalışma Planlayıcı: öğrencinin enrolled kurslarından birini seçerek Google Calendar ve .ics üzerinden haftalık çalışma rutini oluşturmasını sağlar.
Sertifikalar / Arşivmy-courses.certifications, my-courses.archived.
SertifikaKurs bitince otomatik sertifika kaydı; indirme: GET /api/certificates/:id/download → CertificateTemplate ile PDF stream; QR + referans no ile doğrulama.

İlgili mimari: Eğitmen ve Kurs Yönetimi (müfredat, video), Kurs ve Paket Vitrin Sayfaları (course/bundle pazarlama sayfaları), Checkout & Webhooks (kayıt).

  • app/routes/learn.$slug.tsx — Ders izleme paneli (Course Player); loader, müfredat, signedVideoUrl.
  • app/routes/my-courses.learning.tsx — Kurslarım listesi.
  • app/routes/my-courses.tools.tsx — Öğrenci Araçları hub.
  • app/routes/my-courses.tools.calendar.tsx — Akıllı Çalışma Planlayıcı aracı (takvim entegrasyonu).
  • app/routes/api.update-progress.ts — Video izleme ilerlemesi (saniye) kaydı.
  • app/routes/api.video-telemetry.ts — Video telemetri (heartbeat, 15 sn bloklar) + daily_activities + subscription_watch_logs.
  • app/routes/bundle.$slug.tsx — Paket vitrin (layout, PPP, checkout).
  • app/routes/api.certificate.$id.download.ts — Sertifika PDF indirme.
  • app/components/CertificateTemplate.tsx — Sertifika PDF şablonu (@react-pdf/renderer).
  • app/hooks/useLearningTracker.ts — İzlenme süresi heartbeat.
  • app/hooks/useVideoTelemetry.ts — Kayıpsız tampon + 15 sn blok heartbeat (client).
  • app/lib/streak.ts — recordLearningActivity, getStreakData, getWeeklyActivity.
  • app/lib/db-queries.ts — getCompletedLessonIds, getCourseCurriculumWithResources.