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ör —
users.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_hourOtomatik Yenileme Akışı
1
Sayfa yüklenir (08:29)
PHP hesaplar:
(60 - 29) * 60 - saniye + 60 = ~32 dakika → setTimeout2
09:01'de timeout tetiklenir
fetch('/api/feed-partial') → sunucu yeni saatle HTML render eder3
Sessiz güncelleme
#feed-container.innerHTML = yeni HTML → müzik kesilmez, sayfa reload yok4
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 tetiklenirOtomatik yenileme ÇALIŞMAZ
/koleksiyon/{slug} — zaman bağımsızPlaylist/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
| Kolon | Tip | Default | Index | Açıklama |
|---|---|---|---|---|
| sector_id | BIGINT UNSIGNED NULL | NULL | INDEX | FK → muzibu_sectors.sector_id. corporate_account_id sonrası. |
Migration 2
muzibu_content_collections (Yeni)
| Kolon | Tip | Default | Açıklama |
|---|---|---|---|
| collection_id | BIGINT UNSIGNED AUTO | PK | Birincil anahtar |
| title | JSON | — | {"tr": "...", "en": "..."} |
| slug | JSON | — | {"tr": "sabah-kafe-mix"} |
| description | JSON NULL | NULL | Çoklu dil açıklama |
| type | ENUM | — | curated | daypart | sector | featured |
| icon | VARCHAR(50) NULL | NULL | FontAwesome ikon |
| color | VARCHAR(20) NULL | NULL | Hex renk |
| media_id | BIGINT UNSIGNED NULL | NULL | Kapak görseli |
| display_rules | JSON NULL | NULL | {start_hour, end_hour, days, is_always} |
| sector_rules | JSON NULL | NULL | {mode, sector_ids, show_to_all} |
| business_rules | JSON NULL | NULL | {subscription, min_months} |
| priority | INT DEFAULT 0 | INDEX | Büyük = önce göster |
| is_active | BOOLEAN DEFAULT 1 | INDEX | Aktif/Pasif |
| is_featured | BOOLEAN DEFAULT 0 | — | Öne çıkan |
| cache_ttl | INT DEFAULT 300 | — | Cache süresi (saniye) |
| timestamps | TIMESTAMP | — | created_at, updated_at |
Migration 3
muzibu_collection_items (Yeni)
| Kolon | Tip | Default | Açıklama |
|---|---|---|---|
| id | BIGINT UNSIGNED AUTO | PK | Birincil anahtar |
| collection_id | BIGINT UNSIGNED | FK + INDEX | → content_collections.collection_id |
| itemable_type | VARCHAR(255) | COMPOSITE | Playlist | Album | Radio | Genre | Sector |
| itemable_id | BIGINT UNSIGNED | INDEX | Polimorfik FK |
| position | INT DEFAULT 0 | INDEX | Sürükle-bırak sıralama |
| is_active | BOOLEAN DEFAULT 1 | — | Aktif/Pasif |
| timestamps | TIMESTAMP | — | created_at, updated_at |
Unique:
(collection_id, itemable_type, itemable_id)UX Kuralları
| Tip | Tıklanınca | Sayfa 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
| Method | URI | Handler | Açı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