v8 — KESİN PLAN 24 Şubat 2026

Dinamik İçerik Sistemi

Tüm kararlar, DB şeması, dosya haritası, UX kuralları, zaman yönetimi, 18 TODO.

Kesinleşen Kararlar (13 Madde)

B2B Only — Her kullanıcı bir işletme sahibi. Guest/bireysel yok. Soundtrack Your Brand modeli.
5 Polimorfik Tip — Playlist, Album, Radio, Genre, Sector. Aynı koleksiyonda karışık kullanılır.
Users Sektörusers.sector_id FK → muzibu_sectors.sector_id. Kullanıcı profilinden seçer.
Esnek Saat Aralığı — Sabit slot isimleri yok. Admin start_hour ve end_hour seçer. Saat seçici sadece düz saatler: 00:00, 01:00, 02:00 ... 23:00.
Koleksiyon Tipleri — curated (el seçimi), daypart (zamana göre), sector (sektöre göre), featured (öne çıkan).
Tıklama Davranışları — Radio → hemen çalar (sayfa değişmez). Playlist/Album → şarkı listesi açılır. Genre → genre sayfası. Sector → sektör sayfası.
"Tümü" Butonu — Koleksiyonda 6'dan fazla öğe varsa görünür → /koleksiyon/{slug}. 6 ve altı → buton gizli, yatay scroll yeterli.
Sıralama — Koleksiyon içi: position (sürükle-bırak). Koleksiyonlar arası: priority (büyük = önce).
Cache — Key: smart_feed:{userId}:{sectorId}:{saat_block}, TTL: 300 saniye.
Koleksiyon SayfasıGET /koleksiyon/{slug} → Grid görünüm, aynı tıklama kuralları.
Zaman Güncelleme — Tek setTimeout, saat başı +1dk'da tetiklenir. Client RAM: 8 byte. CPU: sıfır.
Otomatik Sessiz Yenileme — Sadece anasayfa #feed-container. fetch('/api/feed-partial')innerHTML swap. Sayfa reload yok, müzik kesilmez. İç sayfalarda hiçbir şey yapma.
Admin Saat Gösterimi — Dropdown'larda sadece düz saatler: 00:00, 01:00, 02:00 ... 23:00. Dakika/yarım saat yok.

Zaman Yönetimi (Detaylı Akış)

Admin Panelde Saat Seçimi

Bu koleksiyon 09:00-14:00 arası görünür
Dropdown seçenekleri: 00:00, 01:00, 02:00, 03:00 ... 22:00, 23:00 (24 seçenek, sadece düz saat)

display_rules JSON Yapısı GÜNCELLENDİ

{
  "start_hour": 9,       // 09:00 (int, 0-23)
  "end_hour": 14,        // 14:00 (int, 0-23)
  "days": ["mon","tue","wed","thu","fri","sat","sun"],
  "is_always": false     // true ise start/end yoksayılır
}
Eski time_slots: ["morning"] kaldırıldı → esnek start_hour / end_hour

Otomatik Yenileme Akışı

1
Sayfa yüklenir (08:29)
PHP hesaplar: (60 - 29) * 60 - saniye + 60 = ~32 dakika → setTimeout
2
09:01'de timeout tetiklenir
fetch('/api/feed-partial') → sunucu yeni saatle HTML render eder
3
Sessiz güncelleme
#feed-container.innerHTML = yeni HTML → müzik kesilmez, sayfa reload yok
4
Yeni timeout kurulur
var ms = (60 - new Date().getMinutes()) * 60000 + 60000 → 10:01 için
Döngü devam eder
Her saat başı +1dk → fetch → swap → yeni timeout. Sayfa açık kaldığı sürece.
Otomatik yenileme ÇALIŞIR
Anasayfa (/) — #feed-container varsa tetiklenir
Otomatik yenileme ÇALIŞMAZ
/koleksiyon/{slug} — zaman bağımsız
Playlist/Album/Genre/Sektör detay — zaman bağımsız

Frontend JS (Tamamı)

// Sadece anasayfada çalışır
(function(){
    var el = document.getElementById('feed-container');
    if (!el) return; // İç sayfada → hiçbir şey yapma

    function refresh(){
        fetch('/api/feed-partial')
            .then(function(r){ return r.text() })
            .then(function(html){
                el.innerHTML = html;
                schedule(); // Yeni timeout kur
            });
    }

    function schedule(){
        var d = new Date();
        var ms = (60 - d.getMinutes()) * 60000 - d.getSeconds() * 1000 + 60000;
        setTimeout(refresh, ms); // Saat başı +1dk
    }

    setTimeout(refresh, {{ $msUntilNextHour }}); // İlk timeout (PHP hesaplar)
})();
Toplam: ~15 satır vanilla JS. Framework yok. Timer yok. Interval yok. RAM: 8 byte.

Veritabanı Şeması (3 Migration)

Migration 1 users → sector_id ekleme
KolonTipDefaultIndexAçıklama
sector_id BIGINT UNSIGNED NULL NULL INDEX FK → muzibu_sectors.sector_id. corporate_account_id sonrası.
Migration 2 muzibu_content_collections (Yeni)
KolonTipDefaultAçıklama
collection_idBIGINT UNSIGNED AUTOPKBirincil anahtar
titleJSON{"tr": "...", "en": "..."}
slugJSON{"tr": "sabah-kafe-mix"}
descriptionJSON NULLNULLÇoklu dil açıklama
typeENUMcurated | daypart | sector | featured
iconVARCHAR(50) NULLNULLFontAwesome ikon
colorVARCHAR(20) NULLNULLHex renk
media_idBIGINT UNSIGNED NULLNULLKapak görseli
display_rulesJSON NULLNULL{start_hour, end_hour, days, is_always}
sector_rulesJSON NULLNULL{mode, sector_ids, show_to_all}
business_rulesJSON NULLNULL{subscription, min_months}
priorityINT DEFAULT 0INDEXBüyük = önce göster
is_activeBOOLEAN DEFAULT 1INDEXAktif/Pasif
is_featuredBOOLEAN DEFAULT 0Öne çıkan
cache_ttlINT DEFAULT 300Cache süresi (saniye)
timestampsTIMESTAMPcreated_at, updated_at
Migration 3 muzibu_collection_items (Yeni)
KolonTipDefaultAçıklama
idBIGINT UNSIGNED AUTOPKBirincil anahtar
collection_idBIGINT UNSIGNEDFK + INDEX→ content_collections.collection_id
itemable_typeVARCHAR(255)COMPOSITEPlaylist | Album | Radio | Genre | Sector
itemable_idBIGINT UNSIGNEDINDEXPolimorfik FK
positionINT DEFAULT 0INDEXSürükle-bırak sıralama
is_activeBOOLEAN DEFAULT 1Aktif/Pasif
timestampsTIMESTAMPcreated_at, updated_at
Unique: (collection_id, itemable_type, itemable_id)

UX Kuralları

TipTıklanıncaSayfa Değişir?
Radyo Hemen çalar Hayır — player bar güncellenir
Playlist Şarkı listesi Evet — playlist detay
Albüm Şarkı listesi Evet — albüm detay
Genre Genre sayfası Evet — genre içerikleri
Sektör Sektör sayfası Evet — sektör içerikleri
6 ve altı öğe
"Tümü" butonu gizli. Yatay scroll yeterli.
7+ öğe
"Tümü" görünür → /koleksiyon/{slug} grid sayfası.

Anasayfa Wireframe

┌─────────────────────────────────────────┐
│ Header  |  Player Bar                   │ ← dokunulmaz
├─────────────────────────────────────────┤
│ ☀️ Günaydın, Ahmet!                     │
│ Kafen için seçilmiş içerikler            │
│                                         │
│ ★ Editörün Seçimi          Tümü ›      │ ← 8 öğe, Tümü VAR
│ [Radio][Playl][Album][Playl][Genre]→  │
│                                         │
│ ☕ Sabah Kafe Mix                       │ ← 4 öğe, Tümü YOK
│ [Radio][Playl][Playl][Album] →          │
│                                         │
│ 👆 Türkçe Pop Klasikler     Tümü ›      │ ← 9 öğe, Tümü VAR
│ [Radio][Playl][Album][Playl][Genre]→  │
├─────────────────────────────────────────┤
│ Footer                                  │ ← dokunulmaz
└─────────────────────────────────────────┘
     ↑                    ↑
  #feed-container     saat başı
  (sadece bu alan     sessiz yenilenir)
  (innerHTML swap)

Dosya & Route Haritası

Modules/Muzibu/
├── database/migrations/
│   ├── ..._add_sector_id_to_users.php              ← M1 central
│   ├── ..._create_content_collections.php           ← M2 central
│   ├── ..._create_collection_items.php              ← M3 central
│   └── tenant/  (aynı 3 dosya)
│
├── App/Models/
│   ├── ContentCollection.php
│   └── CollectionItem.php
│
├── App/Services/
│   └── SmartFeedService.php
│
├── App/Http/Controllers/Front/
│   └── CollectionController.php                   ← show()
│
├── App/Http/Livewire/
│   ├── CollectionComponent.php                    ← Admin liste
│   └── CollectionManageComponent.php              ← Admin form 5 tab
│
└── resources/views/livewire/
    ├── collection-component.blade.php
    └── collection-manage-component.blade.php

resources/views/themes/muzibu/
├── index.blade.php                                 ← güncelleme
├── partials/
│   └── feed-collections.blade.php                  ← YENİ partial (AJAX swap)
└── collection/
    └── show.blade.php                              ← Tümü grid sayfası

app/Models/User.php                               ← sector() ekleme
routes/tenant.php                                  ← route'lar

Route'lar

MethodURIHandlerAçıklama
GET /koleksiyon/{slug} CollectionController@show Koleksiyon grid sayfası
GET /api/feed-partial Closure (HTML partial) Saat başı sessiz yenileme endpoint'i
GET /admin/koleksiyonlar Livewire: CollectionComponent Admin liste
GET /admin/koleksiyonlar/manage/{id?} Livewire: CollectionManageComponent Admin form (5 tab)

TODO — 4 Faz, 18 Madde

Faz 1 — Migration & Modeller
1
Migration: users.sector_id
Central + Tenant. BIGINT UNSIGNED NULL. FK → muzibu_sectors.sector_id.
2
Migration: muzibu_content_collections
Central + Tenant. 15 kolon. display_rules: {start_hour, end_hour, days, is_always}.
3
Migration: muzibu_collection_items
Central + Tenant. Polimorfik. Unique: (collection_id, itemable_type, itemable_id).
4
Model: ContentCollection
$translatable, $casts JSON, items() hasMany, activeItems() scope.
5
Model: CollectionItem
itemable() morphTo. Morph map: playlist, album, radio, genre, sector.
6
User.php: sector() relationship
belongsTo(Sector::class). $fillable'a sector_id ekle.
Faz 2 — SmartFeedService & Backend
7
SmartFeedService
getFeed(User): greeting + koleksiyonlar. 3 filtre: time (start_hour/end_hour), sector, business. Cache: smart_feed:{userId}:{sectorId}:{hour}, TTL: 300s.
8
CollectionController@show
GET /koleksiyon/{slug}. items() eager load + morphTo. "Tümü" grid sayfası.
9
HomeController güncelle
SmartFeedService entegrasyonu. $msUntilNextHour hesaplama. feed-collections partial.
10
API endpoint: /api/feed-partial
HTML partial döner (JSON değil). Saat başı sessiz yenileme için.
11
Route'lar ekle
/koleksiyon/{slug} + /api/feed-partial → tenant.php
Faz 3 — Admin Panel (Livewire)
12
CollectionComponent (Liste)
Tablo: başlık, tip, sektör, saat aralığı, öğe sayısı, öncelik, durum. Filtreler + bulk actions.
13
CollectionManageComponent (Form — 5 Tab)
Tab 1: Temel (title, slug, tip, ikon, renk, media, açıklama). Tab 2: Zaman (is_always, start_hour, end_hour dropdown 00-23 düz saat, günler). Tab 3: Sektör (show_to_all, include/exclude, dual listbox). Tab 4: İş Kuralları (subscription, min_months). Tab 5: İçerikler (5 tip ekleme, Sortable.js sürükle-bırak).
14
Admin route + menü
/admin/koleksiyonlar, /admin/koleksiyonlar/manage/{id?}. Muzibu menüsüne ekle.
Faz 4 — Frontend
15
feed-collections.blade.php (partial)
Koleksiyon döngüsü: greeting + koleksiyon başlıkları + yatay scroll kartlar. AJAX swap için bağımsız partial.
16
index.blade.php güncelle
#feed-container div + @include partial + setTimeout JS (~15 satır vanilla). "Tümü" butonu: items_count > 6.
17
collection/show.blade.php (grid)
"Tümü" tıklanınca açılan grid sayfa. Aynı tıklama kuralları.
18
Radio tıklama: player entegrasyonu
Radio kartına tıklanınca sayfa değişmeden player-core.js üzerinden radyo çalma.
Versiyon Geçmişi
v1 — İlk analiz (Spotify + Apple + Soundtrack benchmark)
v2 — B2B pivot
v3 — Görsel simülasyon
v4 — 5 polimorfik tip kesinleşti
v5 — Örnek veri + Draft admin
v6 — Kullanıcı deneyimi mockup
v7 — İlk kesin plan (6 sabit slot)
v8 — KESİN PLAN: Esnek saat (start_hour/end_hour, düz saat 00-23) + otomatik sessiz yenileme (setTimeout + fetch partial + innerHTML) + 18 TODO