Kesinleşen Kararlar (14 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.
Greeting Sistemi — Koleksiyonlardan bağımsız, hardcoded PHP. Saate göre 4 selamlama + kullanıcı adı + sektör adı. DB sorgusu yok, admin ayarı yok, sadece
SmartFeedService::getGreeting().Greeting Sistemi YENİ
4 Zaman Dilimi
| Saat Aralığı | Selamlama | İkon |
|---|---|---|
| 06:00 - 12:00 | Günaydın | ☀️ |
| 12:00 - 18:00 | İyi günler | 🌤️ |
| 18:00 - 22:00 | İyi akşamlar | 🌅 |
| 22:00 - 06:00 | İyi geceler | 🌙 |
Kişiselleştirilmiş Alt Başlık
☀️
Günaydın, Ahmet!
Kafe & Restoran için seçilmiş içerikler
sector_id: 1
🌤️
İyi günler, Zeynep!
Spor Salonu & Fitness için seçilmiş içerikler
sector_id: 3
🌙
İyi geceler, Can!
Senin için seçilmiş içerikler
sector_id: NULL
SmartFeedService::getGreeting()
public function getGreeting(User $user): array
{
$hour = now()->hour;
if ($hour >= 6 && $hour < 12) $greet = 'Günaydın';
elseif ($hour >= 12 && $hour < 18) $greet = 'İyi günler';
elseif ($hour >= 18 && $hour < 22) $greet = 'İyi akşamlar';
else $greet = 'İyi geceler';
$sector = $user->sector?->title; // eager loaded
return [
'title' => "$greet, {$user->name}!",
'subtitle' => $sector
? "{$sector} için seçilmiş içerikler"
: 'Senin için seçilmiş içerikler',
];
}
Koleksiyonlardan bağımsız. DB sorgusu yok (sector eager loaded). Admin ayarı yok. Saat başı yenilemede greeting de güncellenir (partial içinde).
Saat başı yenilemede greeting de güncellenir
feed-collections.blade.php partial'ı greeting + koleksiyonların ikisini de içerir.
Saat başı innerHTML swap yapıldığında "Günaydın" → "İyi günler" otomatik değişir.
Zaman Yönetimi
Admin Panelde Saat Seçimi
→
Bu koleksiyon 09:00-14:00 arası görünür
Dropdown: 00:00, 01:00, 02:00 ... 23:00 (24 seçenek, sadece düz saat)
display_rules JSON
{
"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
}
Otomatik Sessiz Yenileme Akışı
1
Sayfa yüklenir (08:29)
PHP:
(60 - 29) * 60 - saniye + 60 = ~32dk → setTimeout2
09:01'de timeout tetiklenir
fetch('/api/feed-partial') → sunucu yeni saatle HTML render eder (greeting + koleksiyonlar)3
Sessiz güncelleme
#feed-container.innerHTML = yeni HTML → greeting + koleksiyonlar güncellenir, müzik kesilmez4
Yeni timeout → 10:01 için
Döngü devam. Sayfa açık kaldığı sürece her saat başı +1dk.
Yenileme ÇALIŞIR
Anasayfa (
/) — #feed-container varsaYenileme ÇALIŞMAZ
İç sayfalar (koleksiyon, playlist, albüm, genre, sektör detay)
Frontend JS (Tamamı)
// Sadece anasayfada çalışır (~15 satır, sıfır framework) (function(){ var el = document.getElementById('feed-container'); if (!el) return; function refresh(){ fetch('/api/feed-partial') .then(function(r){ return r.text() }) .then(function(html){ el.innerHTML = html; schedule(); }); } 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 }}); // ilk timeout (PHP hesaplar) })();
RAM: 8 byte. CPU: sıfır. Framework: yok. Interval: yok.
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! │ ← greeting │ Kafe & Restoran için seçilmiş içerikler │ ← sektör adı │ │ │ ★ 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 (greeting + koleksiyonlar) saat başı sessiz 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 ← getGreeting() + getFeed() ├── App/Http/Controllers/Front/ │ └── CollectionController.php ├── App/Http/Livewire/ │ ├── CollectionComponent.php │ └── CollectionManageComponent.php └── 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 ← greeting + koleksiyonlar (AJAX partial) └── collection/ └── show.blade.php app/Models/User.php ← sector() relationship routes/tenant.php ← route'lar
Route'lar
| Method | URI | Handler | Açıklama |
|---|---|---|---|
| GET | /koleksiyon/{slug} | CollectionController@show | Koleksiyon grid |
| GET | /api/feed-partial | Closure (HTML partial) | Saat başı sessiz yenileme |
| 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 + $fillable
belongsTo(Sector::class, 'sector_id', 'sector_id'). $fillable'a sector_id ekle.
Faz 2 — SmartFeedService & Backend
7
SmartFeedService
getGreeting(User): saat→selamlama + isim + sektör. getFeed(User): 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 (greeting + koleksiyonlar). 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ığı (00:00 formatında), öğe sayısı, öncelik, durum. Filtreler + bulk.
13
CollectionManageComponent (Form — 5 Tab)
Tab 1: Temel. Tab 2: Zaman (is_always, start_hour/end_hour dropdown sadece düz saat 00:00-23:00, günler). Tab 3: Sektör (show_to_all, include/exclude, dual listbox). Tab 4: İş Kuralları. Tab 5: İçerikler (5 tip ekleme, Sortable.js).
14
Admin route + menü
/admin/koleksiyonlar, /admin/koleksiyonlar/manage/{id?}. Muzibu menüsüne ekle.
Faz 4 — Frontend
15
feed-collections.blade.php (partial)
Greeting (selamlama + sektör adı) + koleksiyon döngüsü + yatay scroll kartlar. AJAX swap icin bağımsız partial.
16
index.blade.php güncelle
#feed-container + @include partial + setTimeout JS (~15 satır vanilla). "Tümü": 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 — Esnek saat + otomatik sessiz yenileme
v9 — KESİN PLAN: Greeting sistemi eklendi (4 zaman selamlaması + sektör adı, koleksiyonlardan bağımsız, partial içinde)