Files

195 lines
9.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Faz 2 Planı — Marmaris Local
**Önceki:** `prd.md` (MVP — tamamlandı)
**Bu doküman:** MVP sonrası geliştirme sırası
Sıralama mantığı: önce trafik getiren şey (içerik/SEO), sonra geleni tutan şey (etkileşim), sonra parayı getiren şey (gelir modeli — ama listeleme sayısı bir eşiğe ulaşmadan anlamsız), en altta her an eklenebilecek düşük efor altyapı işleri.
---
## Faz 2.1 — İçerik / SEO Motoru (öncelik: yüksek, ilk başlanacak)
Directory sitesi tek başına trafik çekmez — kürasyonlu içerik olmadan sadece Google'ın zaten indekslediği işletmelerin bir kopyası olursun. Bu faz olmadan diğerlerinin anlamı sınırlı.
### Blog
```prisma
model BlogPost {
id String @id @default(cuid())
slug String @unique
titleTr String
titleEn String
titleRu String
contentTr String // MDX/rich text
contentEn String
contentRu String
coverImage String? // Openinary
tags String[]
relatedListingIds String[] // Listing'lere internal link için
publishedAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
deletedAt DateTime?
}
```
- Route: `/blog`, `/blog/[slug]`
- Admin: `/admin/blog` — rich text editör (Tiptap öneri, shadcn ile uyumlu)
- Her yazıda ilgili `Listing`'lere otomatik link kartı (internal linking = SEO)
- `schema.org Article` structured data
**İlk içerik önerisi (mock/gerçek karışık başlanabilir):** "Marmaris'te Nerede Yenir 2026", "Gün Batımı İçin 5 Mekan", "İçmeler mi Yat Limanı mı — Nerede Kalınır"
### Kürasyon Listeleri (Koleksiyonlar)
```prisma
model Collection {
id String @id @default(cuid())
slug String @unique
titleTr String
titleEn String
titleRu String
descriptionTr String
descriptionEn String
descriptionRu String
coverImage String?
listings Listing[] @relation("CollectionListings")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
```
- Route: `/seckiler`, `/secki/[slug]`
- Admin: `/admin/collections` — listeleme seç (multi-select), sürükle-bırak sıralama
- Örnek koleksiyonlar: "Aile Dostu 5 Restoran", "Bütçe Dostu Mekanlar", "Deniz Kenarında Kahvaltı"
- Bu, "Yerel Onaylı" mührünün doğal uzantısı — tekil rozetten küratörlü gruplamaya geçiş
### RSS
- `/feed.xml` — blog yazıları + yeni eklenen "Yerel Onaylı" listelemeler
- Öncelik gerekçesi: kendi başına trafik kanalı değil ama Google News / ileride newsletter otomasyonu (n8n) için altyapı — blog ile aynı sprintte, 1 saatlik ek iş
---
## Faz 2.2 — Etkileşim (öncelik: orta, 2.1 ile paralel gidebilir)
Auth gerektirmeyen, dönüşümü ucuza artıran işler.
### Favorilere Ekle
- **Backend yok** — client-side localStorage, `savedListingIds: string[]`
- Route: `/kaydedilenler` — localStorage'daki ID'leri okuyup listeler
- Paylaşılabilir link: `/kaydedilenler?ids=abc,def,ghi` — arkadaşla link paylaşımı, auth şart değil
### WhatsApp Paylaş
- Her listeleme detay sayfasında `wa.me/?text=...` deep link buton
- Şema değişikliği yok, ~30 dakikalık iş
### Instagram Feed (her listeleme sayfasında)
Sadece anasayfa değil — Instagram hesabı olan **her** listeleme (kategori fark etmez) kendi detay sayfasında son gönderilerini gösterir + profile giden link.
Veri kaynağı: RapidAPI üzerinden bir Instagram scraper endpoint'i (ör. "Instagram Scraper API2" / "Instagram Data" — RapidAPI marketplace'te birden fazla sağlayıcı var, fiyat/limit karşılaştırıp seçilecek). Instagram'ın resmi Graph API'si business-verified hesap + Facebook app review gerektirdiği için, işletme sahiplerinden bu izni almak pratik değil — RapidAPI scraper daha az sürtünmeli.
**Canlı istekte RapidAPI çağırma — YAPMA.** Her sayfa görüntülemesinde çağırırsan hem yavaş hem RapidAPI faturası trafiğe bağlı, öngörülemez olur. Bunun yerine 24 saatte bir çalışan bir arka plan job'ı ile önbelleğe al:
```prisma
model InstagramFeedCache {
id String @id @default(cuid())
listingId String @unique
handle String
posts Json // [{ imageUrl, caption, permalink, postedAt }]
fetchedAt DateTime @default(now())
listing Listing @relation(fields: [listingId], references: [id])
}
```
- `Listing.instagram` alanı zaten PRD'de vardı (handle/URL) — bunu kaynak olarak kullan
- Senkron mekanizması: n8n'de günlük (24h) tetiklenen bir workflow — `instagram` dolu olan tüm `Listing`'leri gez, her biri için RapidAPI'yi çağır, sonucu `POST /api/cron/instagram-sync` (secret header ile korunan internal endpoint) üzerinden `InstagramFeedCache`'e yaz. n8n zaten elinde (auto2.ayris.tech), bu tam onun işi.
- Frontend sadece DB'den okur, hiçbir zaman canlı RapidAPI çağrısı yapmaz — sayfa yükü hızlı kalır
- `fetchedAt` 24 saatten eskiyse UI'da sessizce eski veriyi göstermeye devam et (job bir sonraki döngüde tazeleyecek), kullanıcıya hata gösterme
### Menüyü Gör Butonu
Restoran/bar/club tarzı kategorilerde, işletmenin kendi menü sayfası/PDF'i varsa, listeleme detay sayfasında "Menüyü Gör" butonu → yeni sekmede işletmenin kendi sitesine/menü linkine gönderir.
```prisma
// Listing modeline eklenecek alan
menuUrl String?
```
- Kategoriye göre kod içinde zorlama yok — buton sadece `menuUrl` doluysa görünür, admin doğal olarak bunu sadece ilgili kategorilerde dolduracak
- Admin formunda (`/admin/listings/[id]`) yeni alan: "Menü Linki (opsiyonel)"
### "Yeni Eklenenler" Widget
- Şema değişikliği yok — `Listing.findMany({ orderBy: createdAt desc, take: 6 })`
- Anasayfa + kategori sayfalarında taze içerik sinyali
---
## Faz 2.3 — Gelir Modeli: Öne Çıkan Listeleme (öncelik: koşullu — belirli bir listeleme sayısına ulaşınca anlamlı)
**Ön koşul:** En az ~30-40 aktif "Yerel Onaylı" listeleme olmadan bu faza girmeye gerek yok — az listelemeyle "öne çıkan" kavramının hiçbir ayırt edici gücü olmaz.
```prisma
// Listing modeline eklenecek alanlar
isFeatured Boolean @default(false)
featuredUntil DateTime? // otomatik süre dolumu için
```
- Kategori/anasayfa sıralamasında `isFeatured` önce gelir
- Admin toggle + tarih seçici (`/admin/listings/[id]`)
- Gelir mekanizması: Ayris Tech'in mevcut 40+ müşteri ilişkisiyle doğal satış kanalı — ayrı bir ödeme altyapısı MVP'de gerekmez, manuel faturalama yeterli
---
## Faz 2.4 — Altyapı (öncelik: düşük efor, istenilen an araya sokulabilir)
### PWA
- `manifest.json` + service worker (`next-pwa` veya manuel)
- "Ana ekrana ekle" — turist telefonda kullanıyor, pratik fark yaratır
### Analytics
- Kendi VPS'teki self-hosted panel (Plausible-tarzı, zaten var) — yeni SaaS ödemesi gerekmiyor
- Takip edilecek event'ler: favori ekleme, WhatsApp paylaşım tıklaması, QR kaynak trafiği, kategori bazlı arama
---
## Önerilen Sıra
1. **Blog + Koleksiyonlar** (2.1) — SEO motoru olmadan geri kalanının önemi düşük
2. **Menü butonu + Instagram feed + Favoriler + WhatsApp paylaş + Yeni Eklenenler** (2.2) — düşük-orta efor, hızlı kazanım, 2.1 ile aynı sprint'te bile yapılabilir. Instagram feed'in n8n cron tarafı ayrı bir iş parçası olarak planlanmalı (senkron job önce kurulmalı, sonra frontend)
3. **PWA + Analytics** (2.4) — ne zaman uygun olursa, bağımsız işler
4. **Öne Çıkan Listeleme** (2.3) — listeleme sayısı eşiğe ulaşınca devreye al
## Env Değişkenleri (Faz 2 eklentileri)
```env
RAPIDAPI_KEY=""
RAPIDAPI_INSTAGRAM_HOST="" # seçilen sağlayıcıya göre (ör. instagram-scraper-api2.p.rapidapi.com)
CRON_SECRET="" # /api/cron/instagram-sync endpoint'ini korumak için
```
## Kararlar (önceki açık sorulardan)
- **Blog içeriği:** DeepSeek ile üretilip otomasyonla (n8n) yayınlanacak — akış detayı ayrıca kurulacak
- **Koleksiyon kapak görselleri:** stok veya yerinde çekim fark etmiyor, ikisi de kullanılabilir
- **Öne çıkan listeleme fiyatlandırması:** aylık sabit ücret (süre bazlı değil)
- **RapidAPI sağlayıcısı:** free-tier bir model kullanılacak
**Free-tier RapidAPI konusunda dikkat edilecek nokta:** RapidAPI'deki Instagram scraper'ların çoğunda (ör. "Instagram Scraper API2", "Instagram Looter2") free/basic plan genelde aylık ~50-100 istek civarında sınırlı. 24 saatte bir senkron ile bile, listeleme sayısı arttıkça (30-40 listeleme × günlük = ~900-1200 istek/ay) free tier hızla yetersiz kalır. Pratik yaklaşım:
- Az listelemeyle (MVP/Faz 2 başlangıcı) free tier rahatça yeter
- Listeleme sayısı büyüdükçe ya senkron sıklığını düşür (24h yerine 48-72h), ya sadece `isLocalApproved` olanları senkronla, ya da o noktada ücretli tiere geç
- İlk kurulumda 2-3 free-tier sağlayıcıyı denemek (aynı endpoint şeklini farklı sağlayıcıdan alacağın için `InstagramFeedCache` şeması sağlayıcıdan bağımsız kalacak şekilde tasarlandı) makul bir başlangıç
## Açık Sorular
- [ ] Free-tier adaylarından hangisi seçilecek — birkaçını test edip (aynı listeleme üzerinde) veri kalitesi/limit karşılaştırması yapılabilir