Skip to content

İçerik Moderasyon Sistemi

Statik içerik moderasyonu, küfür ve nefret söylemi kontrolü, API maliyeti olmadan çalışan basit kontrol mekanizması.

Achidemy, kullanıcı tarafından oluşturulan içeriklerde (kurs başlıkları, açıklamalar, mesajlar, yorumlar) küfür ve nefret söylemi kontrolü yapar. Çekirdek sistem, API maliyeti olmadan çalışan statik bir kontrol mekanizmasıdır; kritik alanlarda ise buna ek olarak Cloudflare AI tabanlı akıllı moderasyon katmanı devrededir.

Dosya: app/lib/moderation.ts

moderateContent(text: string): ModerationResult

Parametreler:

ParametreTipAçıklama
textstringKontrol edilecek metin

Dönüş Değeri: ModerationResult

interface ModerationResult {
isAllowed: boolean; // İçerik onaylandı mı?
reason?: string; // Red nedeni (opsiyonel)
severity?: 'low' | 'medium' | 'high'; // Şiddet seviyesi
matchedWord?: string; // Eşleşen kelime (opsiyonel)
}

Türkçe küfürler:

  • Açık küfürler: amk, aq, sikik, yarrak, göt, orospu, piç, vb.
  • Şifreli yazımlar: s1k, s1kt1r, a.q, p1ç, g0t, vb.

İngilizce küfürler:

  • fuck, shit, bitch, ass, dick, cock, pussy, cunt, bastard, vb.
  • terörist, it, köpek, domuz, şerefsiz, vb.
  • Irkçı, dini veya cinsiyetçi ayrımcılık içeren kelimeler
  • Tekrarlayan karakterler: aaaaaa, !!!!!!, vb.
  • URL’ler ve e-posta adresleri (bazı durumlarda)
SeviyeAçıklamaAksiyon
highAçık küfür, nefret söylemiİçerik reddedilir
mediumŞifreli küfür, hafif argoİçerik reddedilir veya uyarı
lowSpam benzeri içerikİçerik onaylanır ama uyarı gösterilebilir

Kurs başlığı kontrolü:

import { moderateContent } from '~/lib/moderation';
const result = moderateContent(courseTitle);
if (!result.isAllowed) {
// Hata mesajı göster
throw new Error(result.reason || 'İçerik moderasyon kontrolünden geçemedi');
}

Mesaj kontrolü:

const result = moderateContent(messageText);
if (!result.isAllowed) {
// Mesaj gönderilemez
return { error: result.reason };
}

Dosyalar:

  • app/routes/instructor.course.$slug.manage.*.tsx
  • app/graphql/schema.ts (GraphQL mutation’lar)

Kontrol edilen alanlar:

  • Kurs başlığı (title)
  • Kurs açıklaması (description)
  • Bölüm başlıkları (section.title)
  • Ders başlıkları (lesson.title)

Dosyalar:

  • workers/chat.ts (Durable Object)
  • app/routes/messages.tsx

Kontrol edilen alanlar:

  • Mesaj metni (message.text)

Dosyalar:

  • app/graphql/schema.ts (review mutation’lar)

Kontrol edilen alanlar:

  • Yorum metni (review.comment)

Dosyalar:

  • app/components/courses/ReportCourseModal.tsx
  • app/routes/course.$id.tsx
  • app/graphql/schema.ts (ReportCourseInput + reportCourse mutation)
  • app/routes/admin.reports.tsx
  • app/routes/admin.reports.$courseId.tsx

Kontrol edilen alanlar:

  • Şikayet nedeni (reason) — copyright, inappropriate, spam, quality, other
  • Detaylı açıklama (description) — ihlalin nerede olduğu, telif sahibi bilgileri vb.

Kurs sayfalarında yer alan “Bu kursu şikayet et (DMCA / İhlal)” butonu, platformun Safe Harbor (Güvenli Liman) yükümlülüklerini karşılamak için tasarlanmış uçtan uca bir şikayet akışını tetikler. Amaç; kullanıcı tarafından oluşturulan içerik (UGC) için şeffaf bir ihlal bildirimi ve denetim süreci sağlamaktır.

1. Veritabanı Katmanı — course_reports

Section titled “1. Veritabanı Katmanı — course_reports”
  • Tablo: course_reports (app/db/schema.ts)
  • Alanlar:
    • id — UUID, primary key
    • courseId — FK → courses.id
    • reporterId — FK → users.id (sadece giriş yapan kullanıcılar şikayet edebilir)
    • reason — kısa kategori (copyright, inappropriate, spam, quality, other)
    • description — opsiyonel serbest metin alanı
    • statuspending | reviewed | resolved | rejected
    • createdAt — oluşturulma zamanı
  • İndeksler:
    • course_reports_course_idx — kurs bazlı sorgular
    • course_reports_status_idx — durum bazlı filtreler (özellikle admin listeleri için)

Bu tablo, öğrenci tarafındaki raporları ve admin tarafındaki inceleme durumlarını tek yerde tutar.

  • Tip tanımı (app/graphql/schema.ts):
    • input ReportCourseInput { courseId: ID!, reason: String!, description: String }
    • reportCourse(input: ReportCourseInput!): Boolean
  • Yetki:
    • Sadece giriş yapmış kullanıcılar mutation çalıştırabilir; aksi durumda "Şikayet oluşturmak için giriş yapmalısınız." hatası döner.
  • Çalışma:
    • Gönderilen courseId, reason, description alanları course_reports tablosuna insert edilir.
    • Otomatik Moderasyon Kalkanı (AI): Aynı kurs için status = "pending" olan şikayet sayısı 5’e ulaştığında kurs otomatik olarak pending_review durumuna alınır, isMarketplaceVisible = false yapılır ve eğitmene uyarı bildirimi (in-app + e-posta) gönderilir. Detay için Yapay Zeka Destekli Moderasyon sayfasına bakın.
  • Modal bileşeni: ReportCourseModal (app/components/courses/ReportCourseModal.tsx)
    • Kurs detay sayfasının (course.$id.tsx) alt kısmına eklenmiştir.
    • Şikayet nedenleri select menüsü: Telif hakkı ihlali (DMCA), uygunsuz/zayıf içerik, spam/yanıltıcı, düşük kalite/bozuk video, diğer.
    • Detaylı açıklama alanı (opsiyonel) ile öğrenci hangi videoda/hangi noktada ihlal gördüğünü yazabilir.
  • Davranış:
    • handleSubmit ile /api/graphql üzerinden reportCourse mutation çağrılır.
    • Başarılı durumda lokalize bir başarı mesajı (toast) gösterilir ve modal kapanır.
    • Hata durumunda lokalize hata mesajı gösterilir; veritabanı kaydı yapılmaz.

4. Admin — Kurs Bazlı Şikayet Listesi (/admin/reports)

Section titled “4. Admin — Kurs Bazlı Şikayet Listesi (/admin/reports)”
  • Liste sayfası: app/routes/admin.reports.tsx
  • URL: /admin/reports
  • Amaç: Her kurs için şikayet yoğunluğunu görmek ve en kritik kurslara öncelik vermek.

Özellikler:

  • Aggregation (kurs bazlı özet):
    • Aynı kursa ait tüm course_reports kayıtları tek bir kart altında toplanır.
    • totalReports — kurs için açılmış toplam şikayet sayısı.
    • pendingReports — durumu pending olan açık şikayet sayısı.
  • Sıralama:
    • Önce pendingReports (azalan),
    • sonra totalReports (azalan),
    • son olarak da en yeni şikayet tarihi (azalan).
  • Arayüz:
    • Kart başlığında kurs adı ve eğitmen bilgisi.
    • Rozetler: Toplam şikayet sayısı ve açık şikayet sayısı.
    • “İncele” butonu ile detay sayfasına (/admin/reports/:courseId) yönlendirme.
    • Kart içindeki tablo satırlarında bireysel raporlar için durum dropdown’u (pending, reviewed, resolved, rejected) ve useFetcher ile anlık güncelleme.

5. Admin — Detaylı İnceleme Sayfası (/admin/reports/:courseId)

Section titled “5. Admin — Detaylı İnceleme Sayfası (/admin/reports/:courseId)”
  • Detay sayfası: app/routes/admin.reports.$courseId.tsx
  • URL: /admin/reports/:courseId
  • AI Paneli: Sayfada Achidemy AI Moderatör bileşeni bulunur; “Tüm Şikayetleri Analiz Et” butonu ile Cloudflare AI (Llama 3.1 8B) üzerinden şikayetlerin özeti, ortak nedenleri, çıkarılan linkler ve kritiklik skoru alınır. Detay için Yapay Zeka Destekli Moderasyon sayfasına bakın.

Loader:

  • İlgili kurs bilgileri (courses + users join): başlık, slug, mevcut durum, isMarketplaceVisible, eğitmen adı ve e‑posta.
  • Kursa ait tüm course_reports kayıtları en yeni tarihten eskiye doğru listelenir.

Sayfa:

  • Üst kısımda kurs başlığı, eğitmen bilgisi, mevcut durum ve açık şikayet sayısı gösterilir.
  • Her bir rapor için:
    • Şikayet nedeni (reason)
    • Açıklama (description)
    • Durum badge’i (pending, reviewed, resolved, rejected)
    • Oluşturulma tarihi ve raporu gönderen kullanıcı bilgisi

Action:

  • Intent: mark_under_review
  • İşlemler:
    • İlgili kurs:
      • status = "pending_review"
      • isMarketplaceVisible = false (pazardan tamamen kaldırılır)
    • O kursa ait ve durumu pending olan tüm course_reports kayıtları reviewed yapılır.
    • Eğitmene, bildirim sistemi üzerinden (notifications tablosu) kursun inceleme altına alındığını ve DMCA/şikayet nedeniyle geçici olarak yayından kaldırıldığını belirten bir mesaj gönderilir. Bildirim dili, getNotificationLangForUser ile ülkesine göre seçilir.

Bu sayede, hukuken ihtiyaç duyulan “şikayet alındı → incelemeye alındı → pazardan kaldırıldı” zinciri, veritabanı + GraphQL + admin paneli + bildirim sistemi ile uçtan uca dokümante ve izlenebilir hale gelir.


  • Perspective API: Google’ın Perspective API’si ile daha gelişmiş moderasyon
  • Custom Model: Kendi ML modeli ile eğitim
  • Moderasyon geçmeyen içerik için kullanıcıya açıklayıcı mesaj
  • İtiraz mekanizması
  • Hafif argo kelimeler için otomatik düzeltme önerileri
  • Benzer kelimeler için alternatif öneriler

  • app/lib/moderation.ts — Moderasyon fonksiyonları
  • app/graphql/schema.ts — GraphQL mutation’larda kullanım
  • workers/chat.ts — Mesaj moderasyonu
  • app/routes/instructor.course.$slug.manage.*.tsx — Kurs içerik moderasyonu

Yapay zeka destekli moderasyon: Otomatik şikayet kalkanı, şikayet analizi ve kurs kalite ön incelemesi için Yapay Zeka Destekli Moderasyon (AI Moderation) sayfasına bakın.


AI Destekli Yorum Moderasyonu (Hybrid Model)

Section titled “AI Destekli Yorum Moderasyonu (Hybrid Model)”

Kurs yorumları (Course Reviews) için hibrit bir moderasyon modeli kullanılır:

  1. Önce statik moderasyon (moderateContent) çalışır — ms seviyesinde hızlıdır.
  2. Yorum statik kontrollerden geçerse, opsiyonel olarak Cloudflare AI üzerinden ek bir inceleme yapılır.

Dosya: app/lib/moderation.ts
Fonksiyon: moderateReviewContent(comment: string): Promise<{ isAllowed: boolean; reason: string }>

Akış:

  1. moderateContent(comment) ile statik kontroller uygulanır.
    • isAllowed: false dönerse AI çağrılmadan doğrudan red verilir.
  2. Yorum boşsa veya sadece whitespace ise yine AI çağrılmaz.
  3. Diğer durumlarda frontend, /api/moderate-text endpoint’ine POST isteği atar:
const res = await fetch("/api/moderate-text", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ text: comment }),
});
const data = await res.json();
return {
isAllowed: data.isAllowed ?? true,
reason: data.reason || "Yorumunuz topluluk kurallarımıza uymuyor.",
};

Dosya: app/routes/api.moderate-text.ts
Endpoint: POST /api/moderate-text

Görev:

  • Cloudflare Workers AI (@cf/meta/llama-3.1-8b-instruct) üzerinden metni analiz eder.
  • Sadece JSON döner:
{ "isAllowed": true, "reason": "" }

veya

{
"isAllowed": false,
"reason": "Yorumunuz hakaret/argo içerdiği için topluluk kurallarımız gereği yayınlanamıyor."
}

Prompt, modelden yalnızca JSON istemek ve markdown’ı yasaklamak üzere kurgulanmıştır; dönen cevapta olası ```json blokları temizlenir, ilk { ile son } arasındaki bölüm parse edilir. Hata durumunda sistem fail-open çalışır (isAllowed: true), böylece AI servisindeki geçici arızalar kullanıcı akışını tamamen kilitlemez.

Bileşen: CourseReviewModalapp/components/courses/CourseReviewModal.tsx
Endpoint: POST /api/graphql (createReview / updateReview mutation’ları)

  1. Kullanıcı yorum yazıp Kaydet / Güncelle butonuna bastığında:
    • handleAction("upsert") içinde önce setLoading(true) ile UI kilitlenir.
    • Ardından await moderateReviewContent(comment, { courseTitle }) çağrılır.
  2. isAllowed === false ise:
    • Loader durdurulur.
    • toast.error(reason) gösterilir.
    • GraphQL mutation gönderilmez, yorum DB’ye yazılmaz.
  3. isAllowed === true ise:
    • createReview veya updateReview mutation’ı /api/graphql üzerinden çalışır.
    • Başarılı sonuçta yorum kaydedilir, sayfa revalidate() ile tazelenir.

/api/moderate-text ve moderateReviewContent wrapper’ı, ileride:

  • Öğrenci–Eğitmen mesajları,
  • Soru–Cevap (Q&A) alanları,
  • Forum tarzı modüller

için de merkezi bir AI moderasyon servisi olarak yeniden kullanılabilir. Bu sayede platform genelinde tutarlı ve merkezi bir içerik güvenliği katmanı sağlanır.