Skip to content

Eğitmen ve Kurs Yönetimi (Instructor Engine)

Kurs oluşturma sihirbazı, müfredat (Section/Lesson), Bunny CDN streaming, performans paneli ve manuel ödeme talebi.

Achidemy’de eğitmenler kurs oluşturur, müfredat ve içerik yönetir, gelir/öğrenci istatistiklerini görür ve biriken bakiyeyi çeker. Bu sayfa Kurs Oluşturma Sihirbazı, Müfredat ve İçerik (Section/Lesson, Bunny CDN), Eğitmen Performans Paneli (Recharts ile analiz) ve Manuel Ödeme Talebi akışını açıklar. Eğitmen panelinde sağ alt köşedeki AI asistan (Achidemy Copilot) için Eğitmen Asistanı (Instructor Copilot) sayfasına; Araçlar menüsündeki AI destekli sosyal medya, pazar öngörüleri, quiz ve müfredat araçları için Eğitmen AI Araçları (Instructor Tools) sayfasına bakın.

Dosya: app/routes/instructor.create.tsx

Eğitmen yeni kurs oluşturmak için adım adım sihirbaz kullanır.

Adımİçerik
1Kurs türü seçimi: “Kurs” (video dersler, sınavlar) veya “Pratik Testi” (şu an devre dışı).
2Kurs başlığı: Metin alanı (max 60 karakter); en az 5 karakter gerekli.
3Kategori: CATEGORIES listesinden ana/alt kategori seçimi; seçim zorunlu.
4Zaman taahhüdü: “0-2 saat”, “2-4 saat”, “5+ saat” (opsiyonel).
  1. Slug üretimi: Başlıktan generateSlug(title) ile URL dostu slug; çakışmayı önlemek için rastgele 5 karakter eklenir: slug-xxxxx.
  2. GraphQL mutation: createCourse(title, slug, category) çağrılır.
  3. Yönlendirme: Başarılıysa eğitmen Hedefler (Goals) sayfasına yönlendirilir:
    /{lang}/instructor/course/{slug}/manage/goals.

Kurs oluşturulduktan sonra CourseEditSidebar ile adım adım tamamlanır:

  • Planlama: Hedefler (goals), Kurs Yapısı (course-structure), Kurulum & Test (setup).
  • İçerik: Filming & Editing (filming), Müfredat (curriculum), Altyazılar (captions).
  • Yayın: Landing sayfası (landing), Fiyatlandırma (pricing), Promosyonlar ve Kuponlar (promotions).

Tamamlanma durumu: goals → learningOutcomes; curriculum → sectionCount/lessonCount; landing → başlık, açıklama, thumbnail, kategori; pricing → fiyat. Promosyonlar sayfasında eğitmen bu kursa özel kupon oluşturup yönetir. Tüm kuponlar (genel + kurs bazlı) ayrıca Eğitmen paneli ana sayfada “Kuponlar” sekmesinde (/instructor?tab=coupons) listelenir ve yönetilir; detay için Kupon Sistemi (Promosyonlar) sayfasına bakın.

Kurs paketleri (bundle) — liste ve yönetim

Section titled “Kurs paketleri (bundle) — liste ve yönetim”

Eğitmen birden fazla kursu tek ürün olarak paket halinde satar.

RouteDosyaAçıklama
/:lang/instructor/bundlesinstructor.bundles._index.tsxPaket listesi; yeni paket oluşturma akışı.
/:lang/instructor/bundles/:id/manageinstructor.bundles.$id.manage.tsxPaket başlığı, açıklama, fiyat, indirim yüzdesi, indirim geçerlilik tarihleri, kurs seçimi, kapak görseli ve promo video (Bunny).

Öğrenci tarafında vitrin: /:lang/bundle/:slugbundle.$slug.tsx (kurs detay sayfasıyla uyumlu layout, PPP fiyatı, checkout). Mimari özet: Kurs ve Paket Vitrin Sayfaları.

Bölüm (Section) ve Ders (Lesson) Hiyerarşisi

Section titled “Bölüm (Section) ve Ders (Lesson) Hiyerarşisi”

Veritabanı:

  • sections: id, course_id, title, order, created_at. Her bölüm bir kursa aittir.
  • lessons: id, section_id, title, bunny_video_id, order, is_preview, duration. Her ders bir bölüme aittir; video varsa bunny_video_id dolu.

Aynı section altında quiz ve coding exercise tabloları da vardır (quizzes, coding_exercisessection_id); müfredat sayfasında bölüm içinde dersler, quiz’ler ve egzersizler birlikte listelenir.

Dosya: app/routes/instructor.course.$slug.manage.curriculum.tsx

  • Bölüm ekleme/düzenleme/silme, sürükle-bırak sıralama (Accordion + order).
  • Ders ekleme: başlık, video yükleme (Bunny), önizleme (isPreview), kaynaklar (lesson_resources).
  • Video yükleme: Bunny Stream API (presigned URL veya upload endpoint) ile yükleme; bunnyVideoId derse kaydedilir.
  • Bunny işlem durumu: fetchBunnyVideoStatus(libraryId, videoId, apiKey) ile status/length çekilir; getBunnyVideoStatusKind ile “processing” | “ready” | “error” gösterilir (lib/bunny-video-status.ts).

Video İçeriklerinin Bunny CDN Üzerinden Streaming Mantığı

Section titled “Video İçeriklerinin Bunny CDN Üzerinden Streaming Mantığı”

Yükleme (eğitmen tarafı) — Direct Upload (TUS) standardı:

  • Müfredat sayfasında her ders kartında DirectVideoUploader bileşeni kullanılır.
    Dosya: app/components/instructor/DirectVideoUploader.tsx
  • Yükleme akışı:
    1. Eğitmen derse ait kartta video dosyasını seçer (ör. 200 MB, 2 GB, 4K video).
    2. Frontend, backend’e POST /api/bunny/create-upload çağrısı yapar:
      Dosya: app/routes/api.bunny.create-upload.ts
      • Yetki: user.role === "instructor" zorunlu (aksi durumda 401 Unauthorized).
      • Env: BUNNY_LIBRARY_ID ve BUNNY_STREAM_API_KEY (veya fallback olarak BUNNY_API_KEY) ile çalışır.
      • Adımlar:
        • Bunny Stream API üzerinden boş bir video kaydı (placeholder) oluşturur (POST /library/{LIBRARY_ID}/videos).
        • Dönen guid alanını Bunny Video ID (videoId) olarak alır.
        • TUS protokolü ile doğrudan yükleme için SHA-256 imzalı bilet (AuthorizationSignature) üretir:
          signatureString = LIBRARY_ID + API_KEY + expirationTime + videoId → SHA-256 → hex string.
        • Frontend’e { videoId, libraryId, signature, expirationTime } döner.
    3. Frontend tarafında tus-js-client ile doğrudan Bunny CDN’e (TUS endpoint) upload başlar:
      • Endpoint: https://video.bunnycdn.com/tusupload
      • Header’lar:
        • AuthorizationSignature: backend’in ürettiği imza
        • AuthorizationExpire: expirationTime
        • VideoId: backend’in oluşturduğu videoId (Bunny guid)
        • LibraryId: BUNNY_LIBRARY_ID
      • Metadata:
        • filetype: file.type
        • title: ders başlığı
      • Özellikler:
        • Büyük dosyalar için uygun: 200 MB, 2 GB veya 4K videolar güvenle yüklenebilir.
        • Devam edebilir (resumable) upload: İnternet kesildiğinde, bağlantı geri geldiğinde upload kaldığı yerden devam eder; TUS protokolü bu davranışı standart olarak sağlar.
        • Retry stratejisi: retryDelays ile ağ hatalarında otomatik tekrar denemeleri yapılandırılmıştır.
    4. Yükleme tamamlandığında DirectVideoUploader parent’a onUploadSuccess(videoId) callback’i ile Bunny Video ID’yi döner.
    5. Müfredat sayfasında handleDirectVideoUploadSuccess fonksiyonu bu ID’yi alır ve GraphQL mutation ile derse yazar:
      • mutation UpdateLessonVideo($id: ID!, $videoId: String!) { updateLesson(id: $id, bunnyVideoId: $videoId) { id bunnyVideoId } }
      • UI tarafında ilgili dersin bunnyVideoId alanı güncellenir ve ders “video yüklü” durumuna geçer.
    6. Aynı akış, mevcut videonun değiştirilmesi için de kullanılır; yeni yüklenen video ID’si eski ID yerine yazılır.

Neden bu sistem? (Standart ve ölçeklenebilir yaklaşım)

  • TUS (Resumable Upload) dünya standardıdır: Büyük video dosyalarında tarayıcıdan doğrudan CDN’e yükleme için yaygın ve güvenilir bir protokoldür.
  • Büyük dosya desteği: 200 MB, 2 GB, 4K gibi yüksek boyutlu videoları backend üzerinden proxy’lemeden, doğrudan Bunny’nin TUS endpoint’ine göndeririz; bu sayede hem performans hem de maliyet açısından ölçeklenebilir bir çözüm elde edilir.
  • Kesintiye dayanıklı: Kullanıcının interneti kesilse bile TUS yükleme oturumu kaybolmaz; bağlantı geri geldiğinde yükleme kaldığı yerden devam eder. Eğitmen uzun videolar yüklerken sayfayı açık tuttuğu sürece, kısa kesintiler kritik değildir.
  • Backend sadece imza ve yetki merkezidir: Sunucu sadece video kaydı oluşturma + imzalı bilet üretme + DB’ye bunnyVideoId yazma görevini üstlenir; binary data hiç sunucuya taşınmaz. Bu, Cloudflare Workers ortamında CPU/bellek kullanımını minimal tutar ve sistem standardı olarak belirlenmiştir.

İzleme (öğrenci tarafı):

Dosya: app/lib/video-security.tsgenerateSignedVideoUrl(videoId, libraryId, securityKey, expirationSeconds)

  • Amaç: Doğrudan indirme ve yetkisiz erişimi engellemek için imzalı URL kullanılır.
  • Algoritma: tokenData = securityKey + videoId + expires → SHA-256 hash → query: token=...&expires=....
  • URL formatı: https://iframe.mediadelivery.net/embed/{libraryId}/{videoId}?token={hash}&expires={expires}.
  • Ortam değişkenleri: BUNNY_LIBRARY_ID, BUNNY_VIDEO_SECURITY_KEY (Pull Zone Remote Auth Key).

Öğrenci sayfası: app/routes/learn.$slug.tsx loader’da müfredat çekilir; her ders için generateSignedVideoUrl ile signedVideoUrl hesaplanır; oynatıcıda bu URL kullanılır. Kayıt/enrollment kontrolü loader’da yapılır.

Bunny durum kodları (lib/bunny-video-status.ts): 0=Queued, 1=Processing, 2=Encoding → “İşleniyor”; 3=Finished, 4=Resolution finished → “Hazır”; 5=Failed, 8=PresignedUploadFailed → “Hata”.

Dosya: app/routes/instructor.payouts.tsx (Payment sekmesi)

Eğitmenler üç farklı yöntemle ödeme doğrulaması yapabilir:

Stripe Connect’in desteklendiği ülkelerde:

  1. Eğitmen “Stripe” seçer
  2. “Stripe ile Banka Bağla” butonuna tıklar
  3. Stripe Onboarding sayfasında hesap bilgilerini tamamlar
  4. Başarılı dönüşte isConnectOnboardingCompleted = true

Stripe Connect’in desteklenmediği ülkelerde (örn. Türkiye) veya Payoneer tercih eden eğitmenler için:

  1. Eğitmen “Payoneer” seçer
  2. KYC formu doldurulur:
    • Yasal Ad ve Soyad (legalName)
    • Ulusal Kimlik / Vergi No (taxId)
    • Payoneer E-posta (payoneerEmail)
    • Kimlik Belgesi Ön Yüz (JPG/PNG/PDF, max 5MB)
    • Kimlik Belgesi Arka Yüz (JPG/PNG/PDF, max 5MB)
  3. Kimlik belgeleri Bunny Storage’a yüklenir: id-cards/{COUNTRY}/{USER_ID}/
  4. kycStatus = "pending", payoutMethod = "payoneer" olarak kaydedilir
  5. Admin /admin/kyc-requests panelinde başvuruyu inceler ve onaylar/reddeder
  6. Onaylanan eğitmen (kycStatus = "approved") artık ödeme talebi oluşturabilir

Mobil ödeme platformu Cenoa’yı tercih eden eğitmenler için:

  1. Eğitmen “Cenoa” seçer
  2. KYC formu doldurulur:
    • Yasal Ad ve Soyad (legalName)
    • Ulusal Kimlik / Vergi No (taxId)
    • Cenoa Telefon Numarası (cenoaPhone) — +90 5XX XXX XX XX formatında
    • Kimlik Belgesi Ön Yüz (JPG/PNG/PDF, max 5MB)
    • Kimlik Belgesi Arka Yüz (JPG/PNG/PDF, max 5MB)
  3. Kimlik belgeleri Bunny Storage’a yüklenir: id-cards/{COUNTRY}/{USER_ID}/
  4. kycStatus = "pending", payoutMethod = "cenoa" olarak kaydedilir
  5. Admin /admin/kyc-requests panelinde başvuruyu inceler ve onaylar/reddeder
  6. Onaylanan eğitmen (kycStatus = "approved") artık ödeme talebi oluşturabilir
// Hangi ödeme yöntemi ile doğrulama yapıldığını belirle
const isStripeVerified = user?.isConnectOnboardingCompleted ?? false;
const isPayoneerVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "payoneer";
const isCenoaVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "cenoa";
// Genel onay durumu
const isApproved = isPayoneerVerified || isCenoaVerified || isStripeVerified;

Her ödeme yöntemi için gerçek marka logoları kullanılır:

{/* Stripe */}
<img src="/images/stripe.webp" alt="Stripe" className="h-10 w-auto object-contain" />
{/* Payoneer */}
<img src="/images/payoneer.png" alt="Payoneer" className="h-8 w-auto object-contain" />
{/* Cenoa */}
<img src="/images/cenoa.png" alt="Cenoa" className="h-8 w-auto object-contain" />

Doğrulama Sonrası UI (Logo + “ile doğrulandı”)

Section titled “Doğrulama Sonrası UI (Logo + “ile doğrulandı”)”
Doğrulama YöntemiUI Görünümü
StripeStripe logo + “ile doğrulandı” (indigo tema) + ödeme talebi formu
PayoneerPayoneer logo + “ile doğrulandı” (turuncu tema) + ödeme talebi formu
CenoaCenoa logo + “ile doğrulandı” (mavi tema) + ödeme talebi formu
DoğrulanmamışYöntem seçimi ve doğrulama formu
Ödeme YöntemiAna RenkTema
Stripeİndigo (#6366F1)bg-indigo-50, text-indigo-600
PayoneerTuruncu (#F97316)bg-orange-50, text-orange-600
CenoaMavi (#0052FF)bg-blue-50, text-blue-600

Detaylı bilgi için KYC Onboarding sayfasına bakın.

Layout: app/routes/instructor.performance.tsx — InstructorSidebar + <Outlet />.
Varsayılan yönlendirme: instructor.performance._index.tsx/{lang}/instructor/performance/students.

Dosya: app/routes/instructor.performance.students.tsx

  • Loader: getInstructorStudents(db, userId) ile son öğrenciler, tüm satın alanlar, iade edilenler, abonelik izleyicileri ve satın alım özeti (subscription, direct, organic, affiliate) alınır.
  • Metrik kartları: Toplam satın alım, aktif öğrenci sayısı, iade edilen sayısı, abonelik izleyicisi sayısı.
  • Sekmeler: Tüm öğrenciler, satın alanlar, iade edilenler, abonelik.
  • Tablo: Öğrenci adı, kurs, satın alma tarihi, satın alma türü (badge). Recharts kullanılmaz; istatistikler kart ve tablo ile sunulur.

Gelir Analizi ve Recharts — Reviews / İzlenme Trendleri

Section titled “Gelir Analizi ve Recharts — Reviews / İzlenme Trendleri”

Dosya: app/components/instructor/PerformanceReviewsCharts.tsx

  • Veri: watchTrends (tarih bazlı toplam / abonelik / satın alım izlenme süreleri) ve courseAnalytics (kurs bazlı trendler).
  • Recharts: Dinamik import ile yüklenir (import("recharts")). Kullanılan bileşenler: LineChart, Line, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer.
  • Grafikler:
    • Çizgi grafiği: Tarih başına toplam / abonelik / satın alım izlenme dakikaları (watchTrends).
    • Çubuk grafiği: Günlük izlenme dakikaları.
  • Filtreler: Dönem (1 hafta, 1 ay, 3 ay, 6 ay); kurs seçimi (courseAnalytics üzerinden).

Bu bileşen instructor.performance.reviews sayfasında kullanılır; eğitmen burada izlenme trendlerini ve dolaylı olarak ilgili gelir/etkileşim analizini görür.

Manuel Ödeme Talebi: Biriken Bakiyeyi Çekme Süreci

Section titled “Manuel Ödeme Talebi: Biriken Bakiyeyi Çekme Süreci”

Dosya: app/routes/instructor.payouts.tsx

  • Loader: getInstructorBalance, getInstructorPayoutRequests, getUserById, getInstructorEarnings, getInstructorSubscriptionWatchByCourse ile bakiye, talep listesi, kullanıcı ödeme ayarları, kazançlar ve abonelik izlenme verileri alınır.
  • Stripe Connect: onboarding=success query parametresi ile dönüldüğünde isConnectOnboardingCompleted ve payoutMethod güncellenir.
  • Toplam bakiye (kurs satışları + affiliate + abonelik payları) yerel para biriminde gösterilir: eğitmenin ülkesine göre TR → ₺ (TRY), Euro bölgesi → € (EUR), diğer → $ (USD). Döviz kurları Döviz Kuru API ile KV’den okunur ve loader’da bakiye hesaplanırken kullanılır.
  • Son kazançlar listesi (earnings): satış türü badge’i (organik, direkt, affiliate, paket, abonelik), orijinal tutar/para birimi ve yerel karşılık (kur bilgisi ile).
  1. Tutar girişi: Minimum çekim tutarı kontrolü ($100 veya eşdeğeri).
  2. Aylık limit: Maksimum 2 talep/ay; aşılmışsa uyarı.
  3. Doğrulama kontrolü:
    • Payoneer doğrulamalı: manualPayoutDetails (Payoneer e-posta/IBAN) dolu olmalı
    • Stripe Connect doğrulamalı: Ek kontrol gerekmez (onboarding tamamlanmış)
  4. Mutation: requestPayout(amount: Float!) GraphQL mutation’ı çağrılır; başarılıysa payout_requests tablosuna kayıt eklenir (status: pending).
  5. Liste: Önceki talepler tabloda gösterilir; durum: pending, processing, completed, rejected.

Doğrulanan yönteme göre alıcı hesap bilgisi gösterilir:

  • Stripe Connect: “Stripe Connect”
  • Payoneer: Payoneer e-posta veya IBAN

Admin panelinde (/admin/payouts) talepler onaylanır. Sistem, eğitmenin doğrulama yöntemini kontrol eder:

const isStripeVerified = instructor?.isConnectOnboardingCompleted ?? false;
const isPayoneerVerified = instructor?.kycStatus === "approved" && !isStripeVerified && instructor?.payoutMethod === "payoneer";
const isCenoaVerified = instructor?.kycStatus === "approved" && !isStripeVerified && instructor?.payoutMethod === "cenoa";
  • Stripe Connect doğrulamalı (isStripeVerified): Stripe transfers.create ile otomatik transfer yapılır
  • Payoneer doğrulamalı (isPayoneerVerified): Stripe Transfer yapılmaz; admin Payoneer/IBAN bilgilerine manuel ödeme yapar, ardından “Onayla” ile talebi tamamlandı olarak işaretler
  • Cenoa doğrulamalı (isCenoaVerified): Stripe Transfer yapılmaz; admin Cenoa telefon numarasına manuel ödeme yapar, ardından “Onayla” ile talebi tamamlandı olarak işaretler

Detaylı bilgi için Türkiye (TR) Özel Payout Akışı ve Stripe Connect Akışı sayfalarına bakın.

KonuAçıklama
Kurs sihirbazıinstructor.create.tsx — 4 adım (tür, başlık, kategori, zaman); createCourse mutation → manage/goals’a yönlendirme.
MüfredatSection/Lesson (ve quiz, coding exercise) hiyerarşisi; curriculum sayfasında CRUD ve sıralama.
VideoBunny CDN: yükleme → bunnyVideoId; izleme → generateSignedVideoUrl ile imzalı embed URL.
PerformansÖğrenci istatistikleri (performance/students); Recharts ile izlenme trendleri (performance/reviews).
Ödeme doğrulamaStripe Connect, Payoneer veya Cenoa/KYC; doğrulama yöntemine göre farklı UI ve akış; logo ile gösterim.
Manuel ödemeinstructor.payouts: bakiye, requestPayout mutation, doğrulama kontrolü ve talep listesi.

Detaylı ödeme akışları için Stripe Connect, TR Payout, KYC Onboarding, Döviz Kuru API ve Checkout & Webhooks sayfalarına bakın.

Bunny CDN geliştirmeleri ve daha fazla bilgi için: Bunny.net Dokümantasyonu
Stripe geliştirmeleri ve daha fazla bilgi için: Stripe Dokümantasyonu

  • app/routes/instructor.create.tsx — Kurs oluşturma sihirbazı.
  • app/routes/instructor.payouts.tsx — Eğitmen ödemeler ve payout talebi.
  • app/routes/instructor.course.$slug.manage.curriculum.tsx — Müfredat ve video yükleme (Bunny).
  • app/routes/instructor.course.$slug.manage.*.tsx — Goals, setup, filming, landing, pricing.
  • app/routes/instructor.bundles._index.tsx, app/routes/instructor.bundles.$id.manage.tsx — Kurs paketleri.
  • app/routes/api.instructor.submit-kyc.ts — KYC başvuru API endpoint’i.
  • app/lib/bunny-video-status.ts — Bunny video durumu; fetchBunnyVideoStatus, getBunnyVideoStatusKind.
  • app/lib/video-security.ts — generateSignedVideoUrl.
  • app/routes/api.lesson-resource-upload.ts — Ders kaynağı yükleme.