feat: implement dynamic categories, admin category CRUD, fix routing and cleanup

This commit is contained in:
AyrisAI
2026-07-13 13:51:48 +03:00
parent 2f2dafcfb9
commit 5b44c78396
54 changed files with 3619 additions and 473 deletions
+158
View File
@@ -0,0 +1,158 @@
# Faz 3 Planı — Marmaris Local
**Önceki:** `faz-2-plan.md`
**Bu doküman:** Faz 2 sonrası — ajans sinerjisi, gelir derinleştirme, AI fark yaratıcı özellik
## Kapsam Dışı (bilinçli karar)
- **Çok-şehirli genişleme** — yapılmayacak. Marmaris Local sadece Marmaris kalacak. Fethiye/Bodrum/Ören/Datça için domain rezerve edilmişti ama her biri kendi bağımsız sitesi olacak, ortak codebase/çoğaltma modeli yok.
- **Kullanıcı yorumu/puanlama sistemi** — ertelendi, sonra tekrar değerlendirilecek.
- **Ayris Booking entegrasyonu / rezervasyon komisyonu** — sonraki aşama, bu fazın kapsamında değil. Şimdiden mimariyi buna göre zorlamıyoruz.
---
## Faz 3.1 — Ajans Sinerjisi (Bölgesel Öneri Widget'ı)
**Neden bu değerli:** Ayris Tech / Muğla Dijital Medya'nın 40+ müşteri ilişkisi, taklit edilemeyecek bir dağıtım kanalı. Müşteri sitelerine gömülen bir widget hem onlara değer katar (ziyaretçi siteden ayrılmadan "yakında nerede yenir" sorusuna cevap bulur) hem Marmaris Local'e gerçek, çeşitli kaynaklardan gelen backlink + trafik sağlar.
### Mekanik
```
<script src="https://marmarislocal.com/widget.js" data-neighborhood="akyaka"></script>
```
- Yeni public endpoint: `GET /api/widget/nearby?neighborhood=X&category=Y` — 3-5 küratörlü (`isLocalApproved`) listeleme döner, CORS açık
- Cloudflare edge cache ile önbelleklenir (mevcut Cloudflare hesabı üzerinden, ekstra maliyet yok)
- Widget kartları: görsel, isim, kategori, Marmaris Local'deki detay sayfasına link
### Kritik kurallar (spam riskine karşı)
Geçmişteki bir SEO projesinde (thedigitalscale.org) alınan manuel spam cezası dersini burada uygula:
1. **Widget her zaman görünür olacak.** Gizli link, `display:none`, mikroskopik footer yazısı — hiçbiri yok. Ziyaretçinin gerçekten göreceği, tıklayabileceği bir kutu.
2. **Opt-in — asla zorla eklenmez.** Müşteriye "ister misin, ücretsiz" diye sorulur. Kabul eden alır, etmeyen için hiçbir şey değişmez.
3. **Sadece alakalı müşterilerde önerilir.** Turizm/yeme-içme/konaklama işletmeleri (Kite Beach, Moy Group, Ayris Apart gibi) — bir avukatlık bürosuna ya da sürücü kursuna bu widget önerilmez, anlamsız ve spam gibi görünür.
4. **Tek yönlü kalır.** Marmaris Local, widget koyan müşterilere geri link vermeye çalışmaz — karşılıklı/döngüsel link değişimi yapılmaz.
### Takip
```prisma
// Listing modeline eklenecek alanlar (ya da ayrı PartnerSite tablosu)
hasWidgetInstalled Boolean @default(false)
widgetInstalledAt DateTime?
widgetSiteUrl String? // hangi müşteri sitesine kurulduğu
```
- Admin panelde basit bir liste: hangi müşteri kabul etti, hangi tarihte kuruldu — "bu ay kaç yeni backlink kazandık" raporlaması için
### Paketleme
Yeni rebrand/redesign işlerinde (Hermes pipeline'ından geçen müşteri) standart teklife opsiyon olarak eklenir: "Marmaris Local'de listelendin + istersen sitene bölgesel öneri widget'ı." Her yeni proje = potansiyel bir yeni listeleme + potansiyel bir yeni widget — rehber büyüdükçe teklif de güçleniyor.
---
## Faz 3.2 — Gelir Derinleştirme
### a) İşletme Analitik Paneli
```prisma
model ListingAnalyticsDaily {
id String @id @default(cuid())
listingId String
date DateTime
views Int @default(0)
whatsappClicks Int @default(0)
phoneClicks Int @default(0)
menuClicks Int @default(0)
listing Listing @relation(fields: [listingId], references: [id])
@@unique([listingId, date])
}
```
- Frontend'de tıklamalar `POST /api/events` ile fire-and-forget loglanır (ham event tablosuna değil, gece bir job günlük satıra toplanır — event tablosu şişmesin)
- **Freemium kanca:** ücretsiz listelemede sadece toplam görüntülenme sayısı görünür, detaylı analitik panel (`isFeatured` = true) pakete dahil — işletme kendi verisini görmek için öne çıkan pakete geçmeye teşvik edilir
- Admin route: `/admin/listings/[id]/analytics` (kendi görebileceği), işletme sahibi tarafı ileride "listing claim" akışı kurulunca eklenir
### b) Etkinlik Takvimi
```prisma
model Event {
id String @id @default(cuid())
slug String @unique
listingId String? // hangi mekanda gerçekleşiyor (opsiyonel)
titleTr String
titleEn String
titleRu String
descriptionTr String
descriptionEn String
descriptionRu String
startDate DateTime
endDate DateTime?
coverImage String? // Openinary
isSponsored Boolean @default(false)
listing Listing? @relation(fields: [listingId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
```
- Route: `/etkinlikler`, `/etkinlik/[slug]`
- Admin: `/admin/events` CRUD
- İçerik kaynağı kısmen otomatikleştirilebilir — n8n + DeepSeek ile partner mekanların Instagram/Facebook etkinlik paylaşımları taranıp taslak çıkarılır (Tarih-Gizem/1 Piksel pipeline'larındaki state-machine pattern'iyle aynı mantık), admin onaylayıp yayınlar
- Gelir: sponsorlu slot (`isSponsored`) ya da küçük listeleme ücreti
- Sezonluk taze içerik = düzenli SEO sinyali, blog'la aynı işlevi görür
### c) Booking Komisyonu
Ertelendi — Ayris Booking entegrasyonuna bağlı, sonraki aşamada ayrıca planlanacak. Şimdiden bir şema/mimari kararı alınmıyor.
---
## Faz 3.3 — AI Plan Oluşturucu
"3 Günlük Marmaris Planı Oluştur" — birkaç hızlı soru (bütçe, aile/çift/arkadaş grubu, ilgi alanı, gün sayısı) → DeepSeek gün gün plan üretir.
### Kritik kural: grounding
LLM'e serbest üretim yaptırılmaz. Akış:
1. Kullanıcı tercihlerine göre `Listing`/`Collection`/`Event` tablolarından aday listesi çekilir (kategori/mahalle/fiyat filtreli)
2. Bu aday listesi DeepSeek'e bağlam olarak verilir
3. Model sadece bu adaylar arasından seçip sıralar ve anlatı yazar — rehberde olmayan bir mekanı asla "önermez"
```prisma
model GeneratedItinerary {
id String @id @default(cuid())
paramsHash String @unique // aynı tercih kombinasyonu = aynı plan, tekrar üretilmez
params Json // budget, groupType, interests, days
content String // üretilen gün-gün plan metni (TR/EN/RU ayrı üretilir)
listingIds String[] // plana giren listelemeler
createdAt DateTime @default(now())
}
```
- Route: `/plan-olustur` (form), `/plan/[id]` (sonuç — paylaşılabilir link)
- `paramsHash` ile önbellekleme — aynı kombinasyon tekrar DeepSeek'e gitmez, maliyet sınırlanır
- Paylaşılabilir link WhatsApp'ta grup planlaması için doğal bir paylaşım nesnesi
- Her üretilen plan Google'da ayrı indekslenen taze bir long-tail sayfa olur ("3 günlük Marmaris balayı planı" gibi aramalar)
- Eşit adaylar arasında seçim mantığı `isFeatured` olanı hafif önceliklendirebilir — öne çıkan pakete kozmetik ötesinde gerçek işlevsel değer katar
---
## Önerilen Sıra
1. **Ajans widget'ı** (3.1) — sıfır maliyet, elindeki müşteri ilişkisini direkt kullanıyor, en hızlı geri dönüş
2. **Etkinlik takvimi** (3.2b) — blog gibi düzenli içerik/SEO kanalı, mevcut otomasyon pattern'lerine (n8n+DeepSeek) uyuyor
3. **İşletme analitik paneli** (3.2a) — öne çıkan paketin değerini artırır, satış konuşmasını güçlendirir
4. **AI plan oluşturucu** (3.3) — en yüksek efor, en görünür fark yaratıcı özellik; diğerleri oturduktan sonra
## Açık Sorular
- [ ] Widget'ı ilk hangi müşterilerde deneyeceğiz — Kite Beach Akyaka / Moy Group / Ayris Apart gibi en alakalı 3-5 müşteriyle pilot mu başlatılır?
- [ ] Etkinlik içeriği için Instagram/Facebook tarama pipeline'ı hangi hesaplardan başlayacak — partner listelemelerin kendi hesapları mı, yoksa genel "Marmaris etkinlik" araması mı?
- [ ] AI plan oluşturucu için DeepSeek maliyeti — `paramsHash` önbellekleme yeterli mi, yoksa günlük/aylık üretim limiti de konulmalı mı?