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.
Kurs Oluşturma Sihirbazı
Section titled “Kurs Oluşturma Sihirbazı”Dosya: app/routes/instructor.create.tsx
Eğitmen yeni kurs oluşturmak için adım adım sihirbaz kullanır.
Adımlar (4 adım)
Section titled “Adımlar (4 adım)”| Adım | İçerik |
|---|---|
| 1 | Kurs türü seçimi: “Kurs” (video dersler, sınavlar) veya “Pratik Testi” (şu an devre dışı). |
| 2 | Kurs başlığı: Metin alanı (max 60 karakter); en az 5 karakter gerekli. |
| 3 | Kategori: CATEGORIES listesinden ana/alt kategori seçimi; seçim zorunlu. |
| 4 | Zaman taahhüdü: “0-2 saat”, “2-4 saat”, “5+ saat” (opsiyonel). |
Oluşturma Akışı
Section titled “Oluşturma Akışı”- Slug üretimi: Başlıktan
generateSlug(title)ile URL dostu slug; çakışmayı önlemek için rastgele 5 karakter eklenir:slug-xxxxx. - GraphQL mutation:
createCourse(title, slug, category)çağrılır. - Yönlendirme: Başarılıysa eğitmen Hedefler (Goals) sayfasına yönlendirilir:
/{lang}/instructor/course/{slug}/manage/goals.
Sonrası: Kurs Yönetim Akışı
Section titled “Sonrası: Kurs Yönetim Akışı”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.
| Route | Dosya | Açıklama |
|---|---|---|
/:lang/instructor/bundles | instructor.bundles._index.tsx | Paket listesi; yeni paket oluşturma akışı. |
/:lang/instructor/bundles/:id/manage | instructor.bundles.$id.manage.tsx | Paket 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/:slug — bundle.$slug.tsx (kurs detay sayfasıyla uyumlu layout, PPP fiyatı, checkout). Mimari özet: Kurs ve Paket Vitrin Sayfaları.
Müfredat ve İçerik
Section titled “Müfredat ve İçerik”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 varsabunny_video_iddolu.
Aynı section altında quiz ve coding exercise tabloları da vardır (quizzes, coding_exercises → section_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;
bunnyVideoIdderse kaydedilir. - Bunny işlem durumu:
fetchBunnyVideoStatus(libraryId, videoId, apiKey)ile status/length çekilir;getBunnyVideoStatusKindile “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ışı:
- Eğitmen derse ait kartta video dosyasını seçer (ör. 200 MB, 2 GB, 4K video).
- 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_IDveBUNNY_STREAM_API_KEY(veya fallback olarakBUNNY_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
guidalanı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.
- Bunny Stream API üzerinden boş bir video kaydı (placeholder) oluşturur (
- Yetki:
- Frontend tarafında
tus-js-clientile doğrudan Bunny CDN’e (TUS endpoint) upload başlar:- Endpoint:
https://video.bunnycdn.com/tusupload - Header’lar:
AuthorizationSignature: backend’in ürettiği imzaAuthorizationExpire:expirationTimeVideoId: backend’in oluşturduğuvideoId(Bunny guid)LibraryId:BUNNY_LIBRARY_ID
- Metadata:
filetype:file.typetitle: 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:
retryDelaysile ağ hatalarında otomatik tekrar denemeleri yapılandırılmıştır.
- Endpoint:
- Yükleme tamamlandığında
DirectVideoUploaderparent’aonUploadSuccess(videoId)callback’i ile Bunny Video ID’yi döner. - Müfredat sayfasında
handleDirectVideoUploadSuccessfonksiyonu 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
bunnyVideoIdalanı güncellenir ve ders “video yüklü” durumuna geçer.
- 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
bunnyVideoIdyazma 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.ts — generateSignedVideoUrl(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”.
Ödeme Yöntemi Doğrulama
Section titled “Ödeme Yöntemi Doğrulama”Dosya: app/routes/instructor.payouts.tsx (Payment sekmesi)
Eğitmenler üç farklı yöntemle ödeme doğrulaması yapabilir:
1. Stripe Connect Doğrulaması
Section titled “1. Stripe Connect Doğrulaması”Stripe Connect’in desteklendiği ülkelerde:
- Eğitmen “Stripe” seçer
- “Stripe ile Banka Bağla” butonuna tıklar
- Stripe Onboarding sayfasında hesap bilgilerini tamamlar
- Başarılı dönüşte
isConnectOnboardingCompleted = true
2. Payoneer / KYC Doğrulaması
Section titled “2. Payoneer / KYC Doğrulaması”Stripe Connect’in desteklenmediği ülkelerde (örn. Türkiye) veya Payoneer tercih eden eğitmenler için:
- Eğitmen “Payoneer” seçer
- 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)
- Yasal Ad ve Soyad (
- Kimlik belgeleri Bunny Storage’a yüklenir:
id-cards/{COUNTRY}/{USER_ID}/ kycStatus = "pending",payoutMethod = "payoneer"olarak kaydedilir- Admin
/admin/kyc-requestspanelinde başvuruyu inceler ve onaylar/reddeder - Onaylanan eğitmen (
kycStatus = "approved") artık ödeme talebi oluşturabilir
3. Cenoa / KYC Doğrulaması
Section titled “3. Cenoa / KYC Doğrulaması”Mobil ödeme platformu Cenoa’yı tercih eden eğitmenler için:
- Eğitmen “Cenoa” seçer
- 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)
- Yasal Ad ve Soyad (
- Kimlik belgeleri Bunny Storage’a yüklenir:
id-cards/{COUNTRY}/{USER_ID}/ kycStatus = "pending",payoutMethod = "cenoa"olarak kaydedilir- Admin
/admin/kyc-requestspanelinde başvuruyu inceler ve onaylar/reddeder - Onaylanan eğitmen (
kycStatus = "approved") artık ödeme talebi oluşturabilir
Doğrulama Durumu Kontrolü
Section titled “Doğrulama Durumu Kontrolü”// Hangi ödeme yöntemi ile doğrulama yapıldığını belirleconst isStripeVerified = user?.isConnectOnboardingCompleted ?? false;const isPayoneerVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "payoneer";const isCenoaVerified = user?.kycStatus === "approved" && !isStripeVerified && user?.payoutMethod === "cenoa";
// Genel onay durumuconst isApproved = isPayoneerVerified || isCenoaVerified || isStripeVerified;Ödeme Yöntemi Seçim UI’ı (Logo ile)
Section titled “Ödeme Yöntemi Seçim UI’ı (Logo ile)”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öntemi | UI Görünümü |
|---|---|
| Stripe | logo + “ile doğrulandı” (indigo tema) + ödeme talebi formu |
| Payoneer | logo + “ile doğrulandı” (turuncu tema) + ödeme talebi formu |
| Cenoa | logo + “ile doğrulandı” (mavi tema) + ödeme talebi formu |
| Doğrulanmamış | Yöntem seçimi ve doğrulama formu |
Renk Temaları
Section titled “Renk Temaları”| Ödeme Yöntemi | Ana Renk | Tema |
|---|---|---|
| Stripe | İndigo (#6366F1) | bg-indigo-50, text-indigo-600 |
| Payoneer | Turuncu (#F97316) | bg-orange-50, text-orange-600 |
| Cenoa | Mavi (#0052FF) | bg-blue-50, text-blue-600 |
Detaylı bilgi için KYC Onboarding sayfasına bakın.
Eğitmen Performans Paneli
Section titled “Eğitmen Performans Paneli”Layout: app/routes/instructor.performance.tsx — InstructorSidebar + <Outlet />.
Varsayılan yönlendirme: instructor.performance._index.tsx → /{lang}/instructor/performance/students.
Öğrenci İstatistikleri — Students
Section titled “Öğrenci İstatistikleri — 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) vecourseAnalytics(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
Sayfa Özeti
Section titled “Sayfa Özeti”- Loader:
getInstructorBalance,getInstructorPayoutRequests,getUserById,getInstructorEarnings,getInstructorSubscriptionWatchByCourseile bakiye, talep listesi, kullanıcı ödeme ayarları, kazançlar ve abonelik izlenme verileri alınır. - Stripe Connect:
onboarding=successquery parametresi ile dönüldüğündeisConnectOnboardingCompletedvepayoutMethodgüncellenir.
Bakiye Gösterimi
Section titled “Bakiye Gösterimi”- 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).
Ödeme Talebi Oluşturma
Section titled “Ödeme Talebi Oluşturma”- Tutar girişi: Minimum çekim tutarı kontrolü ($100 veya eşdeğeri).
- Aylık limit: Maksimum 2 talep/ay; aşılmışsa uyarı.
- Doğrulama kontrolü:
- Payoneer doğrulamalı:
manualPayoutDetails(Payoneer e-posta/IBAN) dolu olmalı - Stripe Connect doğrulamalı: Ek kontrol gerekmez (onboarding tamamlanmış)
- Payoneer doğrulamalı:
- Mutation:
requestPayout(amount: Float!)GraphQL mutation’ı çağrılır; başarılıysapayout_requeststablosuna kayıt eklenir (status: pending). - Liste: Önceki talepler tabloda gösterilir; durum: pending, processing, completed, rejected.
Alıcı Hesap Bilgisi
Section titled “Alıcı Hesap Bilgisi”Doğrulanan yönteme göre alıcı hesap bilgisi gösterilir:
- Stripe Connect: “Stripe Connect”
- Payoneer: Payoneer e-posta veya IBAN
Admin Onay Süreci
Section titled “Admin Onay Süreci”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): Stripetransfers.createile 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.
| Konu | Açıklama |
|---|---|
| Kurs sihirbazı | instructor.create.tsx — 4 adım (tür, başlık, kategori, zaman); createCourse mutation → manage/goals’a yönlendirme. |
| Müfredat | Section/Lesson (ve quiz, coding exercise) hiyerarşisi; curriculum sayfasında CRUD ve sıralama. |
| Video | Bunny CDN: yükleme → bunnyVideoId; izleme → generateSignedVideoUrl ile imzalı embed URL. |
| Performans | Öğrenci istatistikleri (performance/students); Recharts ile izlenme trendleri (performance/reviews). |
| Ödeme doğrulama | Stripe Connect, Payoneer veya Cenoa/KYC; doğrulama yöntemine göre farklı UI ve akış; logo ile gösterim. |
| Manuel ödeme | instructor.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
İlgili Dosyalar
Section titled “İlgili Dosyalar”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.
logo + “ile doğrulandı” (indigo tema) + ödeme talebi formu
logo + “ile doğrulandı” (turuncu tema) + ödeme talebi formu
logo + “ile doğrulandı” (mavi tema) + ödeme talebi formu