Skip to content

Veritabanı Şeması & İlişkiler

Achidemy PostgreSQL (Neon) tablo yapısı, ilişkisel model ve finansal mimari detayları.

Achidemy, veritabanı katmanında PostgreSQL (Neon) ve Type-safe sorgular için Drizzle ORM kullanmaktadır. Bağlantı, geliştirmede doğrudan Neon pooler URL’si veya Hyperdrive üzerinden yapılır; tek mantıksal veritabanı tüm kullanıcı, kurs, organizasyon (B2B) ve finans verisini barındırır. Sistem; eğitim içeriği, kullanıcı ilerlemesi, mesajlaşma, streak, sepet/istek listesi, quiz ve kod egzersizleri ile karmaşık finansal (Stripe Connect, Affiliate, iade) akışları yönetecek şekilde normalize edilmiştir.

Aşağıdaki diyagram, platformun kalbi olan ana tabloları ve aralarındaki yabancı anahtar (Foreign Key) ilişkilerini göstermektedir.

erDiagram
USER ||--o{ COURSE : "eğitmen olarak oluşturur"
USER ||--o{ ENROLLMENT : "öğrenci olarak kaydolur"
USER ||--o{ EARNINGS : "kazanç"
USER ||--o{ PAYOUT_REQUEST : "ödeme talep eder"
USER ||--o{ FOLLOWS : "takip"
USER ||--o{ CONVERSATION : "mesajlaşma"
USER ||--o{ WISHLIST : "istek listesi"
USER ||--o{ CART_ITEM : "sepet"
COURSE ||--o{ SECTION : "içerir"
SECTION ||--o{ LESSON : "içerir"
SECTION ||--o{ QUIZ : "içerir"
SECTION ||--o{ CODING_EXERCISE : "içerir"
LESSON ||--o{ LESSON_CAPTIONS : "altyazı"
COURSE ||--o{ ENROLLMENT : "öğrenci barındırır"
COURSE }o--|| PRICE_TIER : "fiyat katmanı"
PRICE_TIER ||--o{ TIER_PRICES : "para birimi fiyatları"
COURSE_BUNDLE ||--o{ BUNDLE_COURSES : "paket-kurs"
USER ||--o{ CERTIFICATE : "sertifika"
ENROLLMENT ||--o{ EARNINGS : "kazanç kaydı"

TabloAçıklamaÖnemli Alanlar
userBetter Auth uyumlu kullanıcılarid, email, name, role (student/instructor/admin), stripeCustomerId, stripeConnectId, payoutMethod, manualPayoutDetails, country, affiliateCode, username (public profil slug), headline, bio, welcomeMessage, currentStreak, longestStreak, lastActivityDate, totalLearningMinutes, streakFreezeCount, isSubscriber, onboardingCompleted, KYC alanları: kycStatus (none/pending/approved/rejected), legalName, taxId, idDocumentUrl (ön yüz), idDocumentBackUrl (arka yüz), kycRejectionReason
sessionOturum token’larıid, userId, token, expiresAt
accountOAuth ve şifre hesaplarıid, userId, providerId, password
verificationE-posta/OAuth doğrulamaid, identifier, value, expiresAt
TabloAçıklamaÖnemli Alanlar
followsEğitmen takip (public profil)followerId, followingId (unique çift)
conversationsÖğrenci–eğitmen sohbetleriparticipant1Id, participant2Id, lastMessage, isImportant
messagesMesaj içeriklericonversationId, senderId, text, type, mediaUrl, isRead, isDelivered
TabloAçıklamaÖnemli Alanlar
price_tiersFiyat katmanları (Tier 1, Tier 2, …)id, name, order
tier_pricesKatman–para birimi fiyatlarıtierId, currency (usd, try, eur), amount (unique: tierId + currency)
course_pricesKurs/paket çok para birimli fiyat (opsiyonel)courseId/bundleId, currency, amount, countryCode
course_levelsDinamik seviye (beginner, intermediate, expert, all_levels)value, labelTr, labelEn, order, isActive
TabloAçıklamaÖnemli Alanlar
coursesKurslarid, instructorId, title, subtitle, description, price, priceTierId, thumbnailUrl, promoVideoId, status (draft/pending_review/published/archived), category, rating, reviewsCount, slug, learningOutcomes, prerequisites, targetAudience, language, level, isOpenForSubscription
sectionsBölümlercourseId, title, order
lessonsDersler (video)sectionId, title, bunnyVideoId, order, isPreview, duration
lesson_captionsVideo altyazıları (Bunny Stream)lessonId, languageCode, label, bunnyUrl
lesson_resourcesDers ek dosyaları (PDF vb.)lessonId, title, fileUrl, fileSize, fileType
TabloAçıklamaÖnemli Alanlar
quizzesQuiz’ler (section’a bağlı)sectionId, title, description, order, passingScore
quiz_questionsQuiz sorularıquizId, questionText, order
quiz_optionsŞıklarquestionId, optionText, isCorrect
quiz_resourcesQuiz ek dosyalarıquizId, title, fileUrl
user_quiz_attemptsÖğrenci quiz sonuçlarıuserId, quizId, score, passed, correctAnswers, totalQuestions
TabloAçıklamaÖnemli Alanlar
coding_exercisesKodlama egzersizlerisectionId, title, description, language, initialCode, solutionCode, hints, order
coding_exercise_test_casesTest senaryolarıexerciseId, input, expectedOutput, description, hint, isHidden, order
user_exercise_submissionsÖğrenci kod gönderimiuserId, exerciseId, submittedCode, passed, passedTests, totalTests, executionTime
user_coding_progressEgzersiz tamamlama (tekil kayıt)userId, exerciseId, courseId, completed, lastSubmittedCode
exercise_resourcesEgzersiz ek dosyalarıexerciseId, title, fileUrl
TabloAçıklamaÖnemli Alanlar
enrollmentsKurs/paket kayıtlarıuserId, courseId, bundleId, enrolledAt, refundedAt, refundStatus, refundReason, stripePaymentIntentId, stripeCheckoutSessionId, referredBy (affiliate), couponCode, paidAmountMinor, paidCurrency, maxProgressPercentage. Unique: userId + courseId
user_lessons_progressDers ilerlemesi ve izlenen süreuserId, lessonId, courseId, completed, lastWatchedAt, watchedSeconds. Unique: userId + lessonId
daily_activitiesGünlük izleme (streak)userId, date (YYYY-MM-DD), totalWatchedSeconds. Unique: userId + date
watch_time_logsHeartbeat izlenme logları (payout/audit kaynağı)userId, courseId, lessonId, durationSeconds, createdAt
certificatesTamamlanan kurs sertifikalarıuserId, courseId, certificateCode (unique), fullName, courseTitle, instructorName, issueDate
reviewsKurs değerlendirmeleriuserId, courseId, rating, comment
TabloAçıklamaÖnemli Alanlar
questionsKurs sorularıuserId, courseId, lessonId (opsiyonel), videoTimestamp, title, content
answersCevaplarquestionId, userId, content, isInstructorAnswer
course_notesÖğrenci video notlarıuserId, courseId, lessonId, content, videoTimestamp
TabloAçıklamaÖnemli Alanlar
course_reportsKurslar için DMCA / içerik ihlali / spam şikayetlericourseId, reporterId, reason (copyright/inappropriate/spam/quality/other), description, status (pending/reviewed/resolved/rejected), createdAt
TabloAçıklamaÖnemli Alanlar
cart_itemsSepet (kurs veya paket)userId, courseId veya bundleId (biri dolu), appliedCoupon (kupon kodu)
wishlistsİstek listesiuserId, courseId
TabloAçıklamaÖnemli Alanlar
couponsEğitmen kuponlarıcode (unique), instructorId, courseId (null = genel indirim), bundleId, discountType (percentage/fixed_amount/free), discountValue, maxUses, usedCount, expiresAt, isActive. Detay: Kupon Sistemi
TabloAçıklamaÖnemli Alanlar
course_bundlesKurs paketleriinstructorId, title, description, price, discountPercentage, thumbnailUrl, promoVideoId (Bunny), status, slug, discountValidFrom, discountValidUntil (indirim penceresi; vitrinde isBundleDiscountPeriodActive)
bundle_coursesPaket–kurs eşleşmesi (many-to-many)bundleId, courseId
TabloAçıklamaÖnemli Alanlar
earningsKazançlar (satış, abonelik, affiliate)instructorId, courseId, bundleId, totalPrice, instructorShare, platformShare, affiliateShare, affiliateId, saleType, status, currency, couponCode, stripeCheckoutSessionId
payout_requestsÖdeme talepleri (eğitmen/affiliate)instructorId, amount, status, method (stripe/bank), destination, requestType (instructor/affiliate), earningId
subscription_watch_logsAbonelik izlenme (eğitmen payı)userId, instructorId, durationSeconds, date. Unique: userId + instructorId + date
TabloAçıklamaÖnemli Alanlar
organizationsŞirketler / tenant’larid, name, subdomain (unique), allowedDomains (array), seatLimit, demoEndsAt, stripeCustomerId, stripeSubscriptionId, isActive
organization_membersŞirket üyeleri (RBAC)organizationId, userId, role (owner/admin/member). Unique: organizationId + userId
organization_invitationsDavet linkleri ve e-posta davetleriorganizationId, email, role, token (unique), status, expiresAt, invitedBy
learning_pathsŞirkete özel eğitim rotalarıorganizationId, title, description, createdBy
learning_path_coursesRota içi kurslarpathId, courseId, order

Ders videolarına eklenen altyazılar lesson_captions tablosunda tutulur: lesson_id (FK → lessons), language_code, label, isteğe bağlı bunny_url, created_at. Bunny Stream API ile senkron çalışır; detay için Video Altyazıları (Bunny Stream) sayfasına bakın.


  • daily_activities: Öğrencinin günlük izleme süresi (saniye) kaydedilir; streak hesaplaması bu tablo ve user.lastActivityDate / user.currentStreak / user.longestStreak ile yapılır.
  • user.totalLearningMinutes, user.streakFreezeCount: Öğrenci istatistikleri ve streak donması (1 gün kaçırma hakkı) için kullanılır.

Enterprise Anti‑Cheat (Payout Koruması) — Telemetri Seviyesi

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

POST /api/video-telemetry akışında payout’a dahil edilen izlenmeler ek kurallarla sınırlandırılır:

  • Self‑Watch Ban: course.instructorId === userId ise watch_time_logs / subscription_watch_logs yazılmaz (streak için daily_activities yine artar).
  • Human Daily Cap: Kullanıcı başına günlük paid maksimum 28.800 sn (8 saat).
  • Lesson 3x Cap: Ay içinde aynı lessonId için paid maksimum lessons.duration * 3.

  • app/db/schema.ts — Drizzle şema; tüm tablolar ve ilişkiler.
  • app/db/index.ts — getDb, getDbUrl; Neon/Hyperdrive bağlantısı.
  • drizzle.config.ts — Drizzle Kit (migration) yapılandırması.
  • drizzle/ — SQL migration dosyaları.