first commit
This commit is contained in:
256
doc/api.md
Normal file
256
doc/api.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Netflix Scraper API - API Dokümantasyonu
|
||||
|
||||
## Base URL
|
||||
|
||||
```
|
||||
Development: http://localhost:3000
|
||||
Production: https://api.yourdomain.com
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
Tüm API istekleri `X-API-Key` header'ı gerektirir.
|
||||
|
||||
```http
|
||||
X-API-Key: your-api-key-here
|
||||
```
|
||||
|
||||
### API Key Tipleri
|
||||
|
||||
| Key | Kullanım |
|
||||
|-----|----------|
|
||||
| `API_KEY_WEB` | Web frontend |
|
||||
| `API_KEY_MOBILE` | Mobil uygulama |
|
||||
| `API_KEY_ADMIN` | Admin panel |
|
||||
|
||||
---
|
||||
|
||||
## Endpoints
|
||||
|
||||
### POST /api/getinfo
|
||||
|
||||
Netflix URL'sinden içerik bilgisi getirir.
|
||||
|
||||
**Request**
|
||||
|
||||
```http
|
||||
POST /api/getinfo
|
||||
Content-Type: application/json
|
||||
X-API-Key: your-api-key
|
||||
|
||||
{
|
||||
"url": "https://www.netflix.com/tr/title/81616256"
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Success)**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"title": "Hayata Röveşata Çeken Adam",
|
||||
"year": 2022,
|
||||
"plot": "Dünyaya karşı duyduğu öfke ve yaşadığı kederin katılaştırdığı huysuz bir emekli, yaşamına son vermeyi planlar. Ancak hayatına neşeli bir genç aile girince tüm planları suya düşer.",
|
||||
"genres": ["18+", "Komedi"],
|
||||
"cast": ["Tom Hanks", "Mariana Treviño", "Rachel Keller"],
|
||||
"backdrop": "https://occ-0-7335-3467.1.nflxso.net/dnm/api/v6/..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Response (Error)**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": false,
|
||||
"error": {
|
||||
"code": "VALIDATION_ERROR",
|
||||
"message": "Invalid request parameters",
|
||||
"details": {
|
||||
"errors": [
|
||||
{
|
||||
"field": "url",
|
||||
"message": "URL must be a valid Netflix title URL"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Status Codes**
|
||||
|
||||
| Code | Açıklama |
|
||||
|------|----------|
|
||||
| 200 | Başarılı |
|
||||
| 400 | Geçersiz istek |
|
||||
| 401 | API key eksik |
|
||||
| 403 | Geçersiz API key |
|
||||
| 429 | Rate limit aşıldı |
|
||||
| 500 | Sunucu hatası |
|
||||
|
||||
---
|
||||
|
||||
### POST /api/getinfo/async
|
||||
|
||||
Asenkron scraping job'u oluşturur. Büyük ölçekli kullanım için uygundur.
|
||||
|
||||
**Request**
|
||||
|
||||
```http
|
||||
POST /api/getinfo/async
|
||||
Content-Type: application/json
|
||||
X-API-Key: your-api-key
|
||||
|
||||
{
|
||||
"url": "https://www.netflix.com/tr/title/81616256"
|
||||
}
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"jobId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"status": "pending"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Socket ile İzleme**
|
||||
|
||||
Job durumunu Socket.IO ile izleyebilirsiniz:
|
||||
|
||||
```javascript
|
||||
socket.emit('job:subscribe', jobId);
|
||||
socket.on('job:progress', (data) => console.log(data));
|
||||
socket.on('job:completed', (data) => console.log(data));
|
||||
socket.on('job:error', (data) => console.error(data));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /api/jobs/:jobId
|
||||
|
||||
Job durumunu sorgular.
|
||||
|
||||
**Request**
|
||||
|
||||
```http
|
||||
GET /api/jobs/550e8400-e29b-41d4-a716-446655440000
|
||||
X-API-Key: your-api-key
|
||||
```
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"url": "https://www.netflix.com/tr/title/81616256",
|
||||
"status": "completed",
|
||||
"progress": 100,
|
||||
"step": "completed",
|
||||
"result": {
|
||||
"title": "Hayata Röveşata Çeken Adam",
|
||||
"year": 2022,
|
||||
"plot": "...",
|
||||
"genres": ["18+", "Komedi"],
|
||||
"cast": ["Tom Hanks", "Mariana Treviño", "Rachel Keller"],
|
||||
"backdrop": "https://..."
|
||||
},
|
||||
"createdAt": "2025-02-27T10:00:00.000Z",
|
||||
"updatedAt": "2025-02-27T10:00:05.000Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /health
|
||||
|
||||
Basit sağlık kontrolü.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"timestamp": "2025-02-27T10:00:00.000Z",
|
||||
"uptime": 3600
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### GET /ready
|
||||
|
||||
Tüm bağımlılıkların hazır olup olmadığını kontrol eder.
|
||||
|
||||
**Response**
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ready",
|
||||
"timestamp": "2025-02-27T10:00:00.000Z",
|
||||
"checks": {
|
||||
"database": "healthy",
|
||||
"redis": "healthy"
|
||||
},
|
||||
"env": "production"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Error Codes
|
||||
|
||||
| Code | Açıklama |
|
||||
|------|----------|
|
||||
| `MISSING_API_KEY` | API key header'ı eksik |
|
||||
| `INVALID_API_KEY` | Geçersiz API key |
|
||||
| `VALIDATION_ERROR` | İstek parametreleri geçersiz |
|
||||
| `RATE_LIMIT_EXCEEDED` | Genel rate limit aşıldı |
|
||||
| `SCRAPE_RATE_LIMIT_EXCEEDED` | Scraping rate limit aşıldı |
|
||||
| `SCRAPE_ERROR` | Netflix'ten veri çekilemedi |
|
||||
| `JOB_NOT_FOUND` | Job bulunamadı |
|
||||
| `INTERNAL_ERROR` | Beklenmeyen sunucu hatası |
|
||||
|
||||
---
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
### Genel Rate Limit
|
||||
- **Window**: 1 dakika
|
||||
- **Max İstek**: 30 istek/dakika/IP+APIKey
|
||||
|
||||
### Scraping Rate Limit
|
||||
- **Window**: 1 dakika
|
||||
- **Max İstek**: 10 istek/dakika/IP+APIKey
|
||||
|
||||
Rate limit değerleri `.env` dosyasından yapılandırılabilir.
|
||||
|
||||
---
|
||||
|
||||
## Request/Response Formatları
|
||||
|
||||
### Content Data (GetInfoResponse)
|
||||
|
||||
| Alan | Tip | Açıklama |
|
||||
|------|-----|----------|
|
||||
| `title` | string | İçerik başlığı |
|
||||
| `year` | number \| null | Yayın yılı |
|
||||
| `plot` | string \| null | Açıklama/özet |
|
||||
| `genres` | string[] | Tür listesi |
|
||||
| `cast` | string[] | Oyuncu listesi |
|
||||
| `backdrop` | string \| null | Arka plan görseli URL |
|
||||
|
||||
---
|
||||
|
||||
## OpenAPI / Swagger
|
||||
|
||||
OpenAPI spesifikasyonu için: `/api/docs` (yakında)
|
||||
53
doc/decisions/ADR-001-scraper-choice.md
Normal file
53
doc/decisions/ADR-001-scraper-choice.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# ADR-001: Cheerio Scraping Kütüphanesi Seçimi
|
||||
|
||||
## Durum
|
||||
|
||||
Kabul edildi
|
||||
|
||||
## Bağlam
|
||||
|
||||
Netflix içerik sayfalarından HTML parsing ile veri çekmemiz gerekiyor. İki ana seçenek var:
|
||||
|
||||
1. **Cheerio**: Lightweight HTML parser
|
||||
2. **Playwright/Puppeteer**: Headless browser automation
|
||||
|
||||
## Karar
|
||||
|
||||
**Cheerio** seçildi.
|
||||
|
||||
## Gerekçe
|
||||
|
||||
### Cheerio Avantajları
|
||||
- Hafif ve hızlı
|
||||
- Düşük kaynak kullanımı
|
||||
- Basit API
|
||||
- Daha az bağımlılık
|
||||
|
||||
### Playwright Avantajları
|
||||
- JavaScript rendering desteği
|
||||
- Daha güçlü scraping
|
||||
- Dinamik içerik desteği
|
||||
|
||||
### Seçim Nedeni
|
||||
1. Netflix sayfalarının HTML'inde temel veriler mevcut
|
||||
2. Client-side rendering gerektiren kritik veri yok
|
||||
3. Performans öncelikli
|
||||
4. Başlangıç için Cheerio yeterli
|
||||
|
||||
## Sonuçlar
|
||||
|
||||
### Olumlu
|
||||
- Düşük kaynak kullanımı
|
||||
- Hızlı yanıt süresi
|
||||
- Basit bakım
|
||||
|
||||
### Olumsuz
|
||||
- JavaScript rendering gerektiren sayfalar için çalışmayabilir
|
||||
- Netflix client-side rendering'e geçerse güncelleme gerekir
|
||||
|
||||
### Alternatif Plan
|
||||
Eğer Cheerio yetersiz kalırsa Playwright'a geçiş yapılabilir. Altyapı buna uygun hazır.
|
||||
|
||||
## Tarih
|
||||
|
||||
2025-02-27
|
||||
61
doc/decisions/ADR-002-cache-strategy.md
Normal file
61
doc/decisions/ADR-002-cache-strategy.md
Normal file
@@ -0,0 +1,61 @@
|
||||
# ADR-002: Hybrid Cache Stratejisi
|
||||
|
||||
## Durum
|
||||
|
||||
Kabul edildi
|
||||
|
||||
## Bağlam
|
||||
|
||||
Netflix'ten scraped verileri nasıl saklayacağımıza karar vermemiz gerekiyor. Seçenekler:
|
||||
|
||||
1. **Sadece Cache**: Redis'te TTL ile sakla
|
||||
2. **Sadece Database**: PostgreSQL'de kalıcı sakla
|
||||
3. **Hybrid**: Cache + Database birlikte
|
||||
|
||||
## Karar
|
||||
|
||||
**Hybrid Cache Stratejisi** (Cache → DB → Netflix) seçildi.
|
||||
|
||||
## Akış
|
||||
|
||||
```
|
||||
İstek → Redis Cache → Varsa dön
|
||||
→ Yoksa → PostgreSQL → Varsa dön, Cache'e yaz
|
||||
→ Yoksa → Netflix'ten çek → DB'ye yaz → Cache'e yaz → Dön
|
||||
```
|
||||
|
||||
## Gerekçe
|
||||
|
||||
### Sadece Cache Eksikleri
|
||||
- TTL dolduğunda veri kaybı
|
||||
- Yeniden scraping maliyeti
|
||||
|
||||
### Sadece Database Eksikleri
|
||||
- Her istekte DB sorgusu
|
||||
- Daha yavaş yanıt
|
||||
|
||||
### Hybrid Avantajları
|
||||
1. **Hız**: Cache hit durumunda anlık yanıt
|
||||
2. **Kalıcılık**: Veriler DB'de saklanır
|
||||
3. **Verimlilik**: Netflix'e gereksiz istek atılmaz
|
||||
4. **TTL Esnekliği**: Cache süresi ayarlanabilir
|
||||
|
||||
## Sonuçlar
|
||||
|
||||
### Olumlu
|
||||
- En hızlı yanıt süresi (cache hit)
|
||||
- Veri kalıcılığı
|
||||
- Netflix üzerinde minimal yük
|
||||
|
||||
### Olumsuz
|
||||
- İki sistem senkronizasyonu
|
||||
- Daha fazla kod karmaşıklığı
|
||||
|
||||
### Yapılandırma
|
||||
```env
|
||||
REDIS_TTL_SECONDS=604800 # 7 gün
|
||||
```
|
||||
|
||||
## Tarih
|
||||
|
||||
2025-02-27
|
||||
63
doc/decisions/ADR-003-api-key-strategy.md
Normal file
63
doc/decisions/ADR-003-api-key-strategy.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# ADR-003: Named API Keys Stratejisi
|
||||
|
||||
## Durum
|
||||
|
||||
Kabul edildi
|
||||
|
||||
## Bağlam
|
||||
|
||||
API güvenliği için authentication yöntemi belirlememiz gerekiyor. Birden fazla frontend olacak (Web, Mobile, Admin).
|
||||
|
||||
## Seçenekler
|
||||
|
||||
1. **Tek API Key**: Tüm frontend'ler aynı key'i kullanır
|
||||
2. **Named API Keys**: Her frontend için ayrı key
|
||||
3. **Database API Keys**: Key'ler DB'de tutulur, yönetim paneli ile
|
||||
|
||||
## Karar
|
||||
|
||||
**Named API Keys** (.env'de tanımlı) seçildi.
|
||||
|
||||
## Yapılandırma
|
||||
|
||||
```env
|
||||
API_KEY_WEB=web-frontend-key-xxx
|
||||
API_KEY_MOBILE=mobile-app-key-yyy
|
||||
API_KEY_ADMIN=admin-key-zzz
|
||||
```
|
||||
|
||||
## Gerekçe
|
||||
|
||||
### Tek Key Eksikleri
|
||||
- Hangi frontend'in istek attığı belli değil
|
||||
- Tek bir frontend engellenemez
|
||||
- Audit trail yok
|
||||
|
||||
### Database Key Eksikleri
|
||||
- Ekstra kompleksite
|
||||
- Yönetim paneli gerektirir
|
||||
- Başlangıç için overkill
|
||||
|
||||
### Named Keys Avantajları
|
||||
1. **İzlenebilirlik**: Hangi frontend'in istek attığı bilinir
|
||||
2. **Kontrol**: Tek bir frontend'in erişimi kapatılabilir
|
||||
3. **Basitlik**: .env ile yönetim
|
||||
4. **Güvenlik**: Her frontend için ayrı secret
|
||||
|
||||
## Sonuçlar
|
||||
|
||||
### Olumlu
|
||||
- Frontend bazlı rate limiting
|
||||
- Kolay key rotasyonu
|
||||
- Basit yapılandırma
|
||||
|
||||
### Olumsuz
|
||||
- Yeni frontend için .env güncellemesi gerekir
|
||||
- Key yönetimi manuel
|
||||
|
||||
### Gelecek
|
||||
Gerekirse Database API Keys sistemine geçiş yapılabilir.
|
||||
|
||||
## Tarih
|
||||
|
||||
2025-02-27
|
||||
291
doc/ops.md
Normal file
291
doc/ops.md
Normal file
@@ -0,0 +1,291 @@
|
||||
# Netflix Scraper API - Operasyon Dokümantasyonu
|
||||
|
||||
## Hızlı Başlangıç
|
||||
|
||||
### Gereksinimler
|
||||
|
||||
- Docker & Docker Compose
|
||||
- Node.js 20+ (local development için)
|
||||
|
||||
### Tek Komut ile Başlatma (Development)
|
||||
|
||||
```bash
|
||||
# .env dosyasını oluştur
|
||||
cp .env.example .env
|
||||
|
||||
# .env dosyasını düzenle
|
||||
nano .env
|
||||
|
||||
# Başlat
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
```
|
||||
|
||||
**Hepsi bu kadar!** Uygulama şu adreste çalışacak: `http://localhost:3000`
|
||||
|
||||
---
|
||||
|
||||
## Ortamlar
|
||||
|
||||
### Development
|
||||
|
||||
```bash
|
||||
docker compose -f docker-compose.dev.yml up --build
|
||||
```
|
||||
|
||||
Özellikler:
|
||||
- Hot reload aktif
|
||||
- Tüm loglar görünür
|
||||
- Debug modu
|
||||
|
||||
### Production
|
||||
|
||||
```bash
|
||||
docker compose up --build -d
|
||||
```
|
||||
|
||||
Özellikler:
|
||||
- Multi-stage build
|
||||
- Non-root user
|
||||
- Minimal image
|
||||
- Production optimizations
|
||||
|
||||
---
|
||||
|
||||
## Environment Değişkenleri
|
||||
|
||||
### .env Dosyası
|
||||
|
||||
```env
|
||||
# === Server ===
|
||||
PORT=3000
|
||||
NODE_ENV=development
|
||||
|
||||
# === PostgreSQL ===
|
||||
POSTGRES_HOST=postgres
|
||||
POSTGRES_PORT=5432
|
||||
POSTGRES_USER=postgres
|
||||
POSTGRES_PASSWORD=your-secure-password
|
||||
POSTGRES_DB=netflix_scraper
|
||||
|
||||
# === Redis ===
|
||||
REDIS_HOST=redis
|
||||
REDIS_PORT=6379
|
||||
REDIS_TTL_SECONDS=604800
|
||||
|
||||
# === Rate Limiting ===
|
||||
RATE_LIMIT_WINDOW_MS=60000
|
||||
RATE_LIMIT_MAX_REQUESTS=30
|
||||
|
||||
# === API Keys ===
|
||||
API_KEY_WEB=web-key-change-me
|
||||
API_KEY_MOBILE=mobile-key-change-me
|
||||
API_KEY_ADMIN=admin-key-secret
|
||||
```
|
||||
|
||||
### Değişken Açıklamaları
|
||||
|
||||
| Değişken | Açıklama | Varsayılan |
|
||||
|----------|----------|------------|
|
||||
| `PORT` | Sunucu portu | 3000 |
|
||||
| `NODE_ENV` | Ortam (development/production) | development |
|
||||
| `REDIS_TTL_SECONDS` | Cache süresi (saniye) | 604800 (7 gün) |
|
||||
| `RATE_LIMIT_WINDOW_MS` | Rate limit penceresi (ms) | 60000 (1 dk) |
|
||||
| `RATE_LIMIT_MAX_REQUESTS` | Max istek sayısı | 30 |
|
||||
|
||||
---
|
||||
|
||||
## Migration
|
||||
|
||||
Migration'lar otomatik olarak container başlatılırken çalışır. Manuel çalıştırmak için:
|
||||
|
||||
```bash
|
||||
# Container içinde
|
||||
docker compose exec app npx prisma migrate deploy
|
||||
|
||||
# Local
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
### Yeni Migration Oluşturma
|
||||
|
||||
```bash
|
||||
npx prisma migrate dev --name description_of_change
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Loglama
|
||||
|
||||
Tüm loglar JSON formatında structured logging ile yazılır.
|
||||
|
||||
### Log Seviyeleri
|
||||
|
||||
| Seviye | Açıklama |
|
||||
|--------|----------|
|
||||
| `debug` | Detaylı debug bilgisi |
|
||||
| `info` | Genel bilgi |
|
||||
| `warn` | Uyarı |
|
||||
| `error` | Hata |
|
||||
|
||||
### Log Formatı
|
||||
|
||||
```json
|
||||
{
|
||||
"timestamp": "2025-02-27T10:00:00.000Z",
|
||||
"level": "info",
|
||||
"message": "Server started",
|
||||
"service": "netflix-scraper-api",
|
||||
"port": 3000
|
||||
}
|
||||
```
|
||||
|
||||
### Log Seviyesi Ayarlama
|
||||
|
||||
```env
|
||||
LOG_LEVEL=debug
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Health Check
|
||||
|
||||
### Endpoints
|
||||
|
||||
- `GET /health` - Basit sağlık kontrolü
|
||||
- `GET /ready` - Bağımlılık kontrolü (DB + Redis)
|
||||
|
||||
### Docker Health Check
|
||||
|
||||
Container'lar otomatik health check ile izlenir:
|
||||
|
||||
```yaml
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "--spider", "http://localhost:3000/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Database Bağlantı Hatası
|
||||
|
||||
```
|
||||
Error: Can't reach database server
|
||||
```
|
||||
|
||||
**Çözüm:**
|
||||
1. PostgreSQL container'ın çalıştığını kontrol et
|
||||
2. `POSTGRES_HOST` değerini kontrol et (docker'da `postgres` olmalı)
|
||||
3. Health check'i bekle: `docker compose logs postgres`
|
||||
|
||||
### Redis Bağlantı Hatası
|
||||
|
||||
```
|
||||
Error: Redis connection failed
|
||||
```
|
||||
|
||||
**Çözüm:**
|
||||
1. Redis container'ın çalıştığını kontrol et
|
||||
2. `REDIS_HOST` değerini kontrol et
|
||||
|
||||
### Migration Hatası
|
||||
|
||||
```
|
||||
Error: P3005: The database schema is not empty
|
||||
```
|
||||
|
||||
**Çözüm:**
|
||||
```bash
|
||||
# Veritabanını sıfırla
|
||||
docker compose down -v
|
||||
docker compose up --build
|
||||
```
|
||||
|
||||
### Port Kullanımda Hatası
|
||||
|
||||
```
|
||||
Error: port is already allocated
|
||||
```
|
||||
|
||||
**Çözüm:**
|
||||
```bash
|
||||
# Hangi process kullanıyor
|
||||
lsof -i :3000
|
||||
|
||||
# Process'i durdur
|
||||
kill -9 <PID>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Yararlı Komutlar
|
||||
|
||||
### Container Yönetimi
|
||||
|
||||
```bash
|
||||
# Container'ları durdur
|
||||
docker compose down
|
||||
|
||||
# Container'ları sil (volumes dahil)
|
||||
docker compose down -v
|
||||
|
||||
# Logları görüntüle
|
||||
docker compose logs -f app
|
||||
|
||||
# Container içine gir
|
||||
docker compose exec app sh
|
||||
```
|
||||
|
||||
### Database İşlemleri
|
||||
|
||||
```bash
|
||||
# Prisma studio aç
|
||||
docker compose exec app npx prisma studio
|
||||
|
||||
# Database schema görüntüle
|
||||
docker compose exec app npx prisma db pull
|
||||
|
||||
# Seed çalıştır
|
||||
docker compose exec app npm run prisma:seed
|
||||
```
|
||||
|
||||
### Redis İşlemleri
|
||||
|
||||
```bash
|
||||
# Redis CLI
|
||||
docker compose exec redis redis-cli
|
||||
|
||||
# Tüm cache'i temizle
|
||||
docker compose exec redis redis-cli FLUSHALL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backup & Restore
|
||||
|
||||
### PostgreSQL Backup
|
||||
|
||||
```bash
|
||||
docker compose exec postgres pg_dump -U postgres netflix_scraper > backup.sql
|
||||
```
|
||||
|
||||
### PostgreSQL Restore
|
||||
|
||||
```bash
|
||||
cat backup.sql | docker compose exec -T postgres psql -U postgres netflix_scraper
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Monitoring (Opsiyonel)
|
||||
|
||||
### Metrics Endpoint (Yakında)
|
||||
|
||||
```
|
||||
GET /metrics
|
||||
```
|
||||
|
||||
Prometheus uyumlu metrics döndürür.
|
||||
82
doc/overview.md
Normal file
82
doc/overview.md
Normal file
@@ -0,0 +1,82 @@
|
||||
# Netflix Scraper API - Proje Özeti
|
||||
|
||||
## Proje Hakkında
|
||||
|
||||
Netflix Scraper API, Netflix içerik sayfalarından film/dizi bilgilerini çeken bir backend servisidir. URL gönderilerek içerik bilgileri çekilir, önbelleğe alınır ve geri döndürülür.
|
||||
|
||||
## Neden Oluşturuldu?
|
||||
|
||||
- **Otomatik İçerik Toplama**: Netflix URL'lerinden otomatik olarak içerik bilgisi çekmek
|
||||
- **Performans**: Redis cache ile tekrarlayan isteklerde hızlı yanıt
|
||||
- **Kalıcılık**: PostgreSQL ile verilerin kalıcı olarak saklanması
|
||||
- **Gerçek Zamanlı**: Socket.IO ile canlı ilerleme bildirimleri
|
||||
|
||||
## Teknoloji Yığını
|
||||
|
||||
| Katman | Teknoloji |
|
||||
|--------|-----------|
|
||||
| Runtime | Node.js 20+ |
|
||||
| Framework | Express.js |
|
||||
| Database | PostgreSQL 16 |
|
||||
| Cache | Redis 7 |
|
||||
| Real-time | Socket.IO |
|
||||
| Scraper | Cheerio |
|
||||
| ORM | Prisma |
|
||||
|
||||
## Mimari
|
||||
|
||||
```
|
||||
┌─────────────────┐
|
||||
│ Frontend │
|
||||
│ (Web/Mobile) │
|
||||
└────────┬────────┘
|
||||
│ HTTP + API Key
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────┐
|
||||
│ Express API │────▶│ Redis │
|
||||
│ (Port 3000) │ │ (Cache) │
|
||||
└────────┬────────┘ └─────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────┐ ┌─────────────┐
|
||||
│ Scraper Service │────▶│ PostgreSQL │
|
||||
│ (Cheerio) │ │ (Data) │
|
||||
└─────────────────┘ └─────────────┘
|
||||
```
|
||||
|
||||
## Veri Akışı
|
||||
|
||||
1. **İstek Alınır**: `POST /api/getinfo` endpoint'ine URL gönderilir
|
||||
2. **Cache Kontrolü**: Redis'te veri var mı diye bakılır
|
||||
3. **DB Kontrolü**: PostgreSQL'de veri var mı diye bakılır
|
||||
4. **Scraping**: Netflix'ten veri çekilir (cache/DB'de yoksa)
|
||||
5. **Kaydetme**: Veri DB'ye ve cache'e yazılır
|
||||
6. **Yanıt**: JSON formatında veri döndürülür
|
||||
|
||||
## Önemli Kararlar
|
||||
|
||||
### ADR-001: Cheerio Seçimi
|
||||
Playwright yerine Cheerio seçildi. Başlangıç için yeterli, daha hafif ve hızlı. Gerekirse Playwright'a geçiş yapılabilir.
|
||||
|
||||
### ADR-002: Hybrid Cache Stratejisi
|
||||
Cache → DB → Netflix sıralaması ile veri alınır. Bu sayede:
|
||||
- En hızlı yanıt cache'ten gelir
|
||||
- Cache'te yoksa DB'den gelir
|
||||
- İlk istek hariç Netflix'e istek atılmaz
|
||||
|
||||
### ADR-003: Named API Keys
|
||||
Her frontend için ayrı API key kullanılır. Bu sayede:
|
||||
- Hangi frontend'in istek attığı takip edilebilir
|
||||
- Gerekirse tek bir frontend'in erişimi kapatılabilir
|
||||
|
||||
## Güncellemeler
|
||||
|
||||
| Tarih | Güncelleme |
|
||||
|-------|-----------|
|
||||
| 2025-02-27 | Proje oluşturuldu |
|
||||
|
||||
## İlgili Dokümanlar
|
||||
|
||||
- [API Dokümantasyonu](./api.md)
|
||||
- [Operasyon Notları](./ops.md)
|
||||
- [Socket Events](./socket-events.md)
|
||||
256
doc/socket-events.md
Normal file
256
doc/socket-events.md
Normal file
@@ -0,0 +1,256 @@
|
||||
# Netflix Scraper API - Socket.IO Events
|
||||
|
||||
## Bağlantı
|
||||
|
||||
### URL
|
||||
```
|
||||
ws://localhost:3000
|
||||
```
|
||||
|
||||
### Transport
|
||||
- WebSocket
|
||||
- Polling (fallback)
|
||||
|
||||
---
|
||||
|
||||
## Client → Server Events
|
||||
|
||||
### `job:subscribe`
|
||||
|
||||
Bir job'ın güncellemelerine abone ol.
|
||||
|
||||
**Payload:**
|
||||
```typescript
|
||||
jobId: string
|
||||
```
|
||||
|
||||
**Örnek:**
|
||||
```javascript
|
||||
socket.emit('job:subscribe', '550e8400-e29b-41d4-a716-446655440000');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `job:unsubscribe`
|
||||
|
||||
Job aboneliğini iptal et.
|
||||
|
||||
**Payload:**
|
||||
```typescript
|
||||
jobId: string
|
||||
```
|
||||
|
||||
**Örnek:**
|
||||
```javascript
|
||||
socket.emit('job:unsubscribe', '550e8400-e29b-41d4-a716-446655440000');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Server → Client Events
|
||||
|
||||
### `job:progress`
|
||||
|
||||
Job ilerleme durumu güncellendiğinde gönderilir.
|
||||
|
||||
**Payload:**
|
||||
```typescript
|
||||
{
|
||||
jobId: string;
|
||||
progress: number; // 0-100 arası
|
||||
status: string; // "pending" | "processing" | "completed" | "failed"
|
||||
step: string; // Mevcut adım açıklaması
|
||||
}
|
||||
```
|
||||
|
||||
**Örnek:**
|
||||
```json
|
||||
{
|
||||
"jobId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"progress": 50,
|
||||
"status": "processing",
|
||||
"step": "Scraping Netflix"
|
||||
}
|
||||
```
|
||||
|
||||
**Adımlar:**
|
||||
| Step | Progress | Açıklama |
|
||||
|------|----------|----------|
|
||||
| `created` | 0 | Job oluşturuldu |
|
||||
| `checking_cache` | 10 | Cache kontrol ediliyor |
|
||||
| `checking_database` | 30 | Database kontrol ediliyor |
|
||||
| `scraping_netflix` | 50 | Netflix'ten veri çekiliyor |
|
||||
| `saving_to_database` | 80 | Veritabanına kaydediliyor |
|
||||
| `completed` | 100 | İşlem tamamlandı |
|
||||
|
||||
---
|
||||
|
||||
### `job:completed`
|
||||
|
||||
Job başarıyla tamamlandığında gönderilir.
|
||||
|
||||
**Payload:**
|
||||
```typescript
|
||||
{
|
||||
jobId: string;
|
||||
data: GetInfoResponse;
|
||||
source: "cache" | "database" | "netflix";
|
||||
}
|
||||
```
|
||||
|
||||
**Örnek:**
|
||||
```json
|
||||
{
|
||||
"jobId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"data": {
|
||||
"title": "Hayata Röveşata Çeken Adam",
|
||||
"year": 2022,
|
||||
"plot": "...",
|
||||
"genres": ["18+", "Komedi"],
|
||||
"cast": ["Tom Hanks", "Mariana Treviño", "Rachel Keller"],
|
||||
"backdrop": "https://..."
|
||||
},
|
||||
"source": "netflix"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `job:error`
|
||||
|
||||
Job sırasında hata oluştuğunda gönderilir.
|
||||
|
||||
**Payload:**
|
||||
```typescript
|
||||
{
|
||||
jobId: string;
|
||||
error: {
|
||||
code: string;
|
||||
message: string;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Örnek:**
|
||||
```json
|
||||
{
|
||||
"jobId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"error": {
|
||||
"code": "SCRAPE_ERROR",
|
||||
"message": "Failed to fetch Netflix page: 403"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Kullanım Örneği
|
||||
|
||||
### JavaScript (Browser)
|
||||
|
||||
```javascript
|
||||
import { io } from 'socket.io-client';
|
||||
|
||||
// Bağlan
|
||||
const socket = io('http://localhost:3000');
|
||||
|
||||
// Bağlantı başarılı
|
||||
socket.on('connect', () => {
|
||||
console.log('Connected:', socket.id);
|
||||
});
|
||||
|
||||
// Job oluştur (API üzerinden)
|
||||
const response = await fetch('http://localhost:3000/api/getinfo/async', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-API-Key': 'your-api-key'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
url: 'https://www.netflix.com/tr/title/81616256'
|
||||
})
|
||||
});
|
||||
|
||||
const { data: { jobId } } = await response.json();
|
||||
|
||||
// Job'a abone ol
|
||||
socket.emit('job:subscribe', jobId);
|
||||
|
||||
// İlerleme dinle
|
||||
socket.on('job:progress', (data) => {
|
||||
console.log(`Progress: ${data.progress}% - ${data.step}`);
|
||||
});
|
||||
|
||||
// Tamamlanma dinle
|
||||
socket.on('job:completed', (data) => {
|
||||
console.log('Completed:', data.data);
|
||||
socket.emit('job:unsubscribe', jobId);
|
||||
});
|
||||
|
||||
// Hata dinle
|
||||
socket.on('job:error', (data) => {
|
||||
console.error('Error:', data.error);
|
||||
});
|
||||
```
|
||||
|
||||
### React Hook
|
||||
|
||||
```typescript
|
||||
import { useEffect, useState } from 'react';
|
||||
import { io, Socket } from 'socket.io-client';
|
||||
|
||||
interface JobProgress {
|
||||
jobId: string;
|
||||
progress: number;
|
||||
status: string;
|
||||
step: string;
|
||||
}
|
||||
|
||||
export function useJobProgress(jobId: string | null) {
|
||||
const [progress, setProgress] = useState<JobProgress | null>(null);
|
||||
const [data, setData] = useState<any>(null);
|
||||
const [error, setError] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!jobId) return;
|
||||
|
||||
const socket = io('http://localhost:3000');
|
||||
|
||||
socket.emit('job:subscribe', jobId);
|
||||
|
||||
socket.on('job:progress', setProgress);
|
||||
socket.on('job:completed', (result) => {
|
||||
setData(result.data);
|
||||
socket.emit('job:unsubscribe', jobId);
|
||||
});
|
||||
socket.on('job:error', (err) => {
|
||||
setError(err.error);
|
||||
socket.emit('job:unsubscribe', jobId);
|
||||
});
|
||||
|
||||
return () => {
|
||||
socket.emit('job:unsubscribe', jobId);
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [jobId]);
|
||||
|
||||
return { progress, data, error };
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## CORS Yapılandırması
|
||||
|
||||
Production'da CORS ayarlarını yapılandırın:
|
||||
|
||||
```typescript
|
||||
// src/config/socket.ts
|
||||
io = new Server(httpServer, {
|
||||
cors: {
|
||||
origin: ['https://yourdomain.com', 'https://admin.yourdomain.com'],
|
||||
methods: ['GET', 'POST'],
|
||||
credentials: true,
|
||||
},
|
||||
});
|
||||
```
|
||||
Reference in New Issue
Block a user