308 lines
7.5 KiB
Markdown
308 lines
7.5 KiB
Markdown
# Comment API iskeleti
|
||
|
||
Node.js + Express + Socket.io + Supabase + Redis tabanlı login/register API iskeleti. Güvenlik odaklı, host allowlist, rate limit, admin rol kilidi ve Redis oturum yönetimi içerir. Docker-compose ile Redis + API birlikte ayağa kalkar.
|
||
|
||
## Dizın yapısı
|
||
|
||
```
|
||
src/
|
||
app.js // Express app
|
||
server.js // HTTP + Socket.io bootstrap
|
||
config/
|
||
env.js // .env yükleme ve parse
|
||
supabase.js // Supabase client
|
||
redis.js // Redis client
|
||
middleware/
|
||
authRequired.js
|
||
adminRequired.js
|
||
rateLimit.js
|
||
hostOnly.js
|
||
errorHandler.js
|
||
routes/
|
||
auth.routes.js
|
||
health.routes.js
|
||
controllers/
|
||
auth.controller.js
|
||
services/
|
||
auth.service.js
|
||
session.service.js
|
||
adminLock.service.js
|
||
utils/
|
||
crypto.js // bcrypt + JWT yardımcıları
|
||
response.js // standart JSON çıktı
|
||
```
|
||
|
||
## Çalıştırma
|
||
|
||
1. Bağımlılıklar:
|
||
```bash
|
||
npm install
|
||
```
|
||
> Not: `metascraper` bağımlılığı özel git repo’dan gelir (`git+https://gitea.wisecolt-panda.net/wisecolt/metascraper.git`). Erişim için uygun token/SSH anahtarı olan bir ortamda `npm install` çalıştırın.
|
||
> Docker build için base imaja `git` eklenmiştir; özel repo erişimi için gerekirse `GIT_SSH_COMMAND` veya `GITHUB_TOKEN` benzeri auth ekleyin.
|
||
2. Ortam değişkenleri: `.env.example` dosyasını `.env` olarak kopyalayıp doldurun.
|
||
3. Geliştirme:
|
||
```bash
|
||
npm run dev
|
||
```
|
||
4. Üretim:
|
||
```bash
|
||
npm start
|
||
```
|
||
|
||
## Docker
|
||
|
||
Yerelde (dev, hot-reload) Redis ile birlikte çalıştırmak için:
|
||
|
||
```bash
|
||
docker compose -f docker-compose.dev.yml up --build
|
||
# durdurmak için
|
||
docker compose -f docker-compose.dev.yml down
|
||
```
|
||
|
||
- API: `http://localhost:${PORT}`
|
||
- Redis: `redis://localhost:6379` (container içinde `redis://redis:6379`)
|
||
|
||
### Prod modunda Docker
|
||
|
||
1. `.env` içinde `NODE_ENV=production` ve mümkünse `COOKIE_SECURE=true`, `TRUST_PROXY=loopback` (veya proxy sayısı) olarak ayarlayın.
|
||
2. Çalıştırın:
|
||
```bash
|
||
docker compose up --build -d
|
||
```
|
||
3. Durdurun:
|
||
```bash
|
||
docker compose down
|
||
```
|
||
|
||
## .env örneği
|
||
|
||
```
|
||
NODE_ENV=development
|
||
PORT=3000
|
||
TRUST_PROXY=loopback
|
||
|
||
SUPABASE_URL=https://YOUR-SUPABASE-PROJECT.supabase.co
|
||
SUPABASE_SERVICE_ROLE_KEY=YOUR_SUPABASE_SERVICE_ROLE_KEY
|
||
|
||
REDIS_URL=redis://redis:6379
|
||
|
||
JWT_SECRET=supersecretjwt
|
||
JWT_EXPIRES_IN=7d
|
||
COOKIE_NAME=sid
|
||
COOKIE_SECURE=false
|
||
|
||
ADMIN_EMAIL=admin@example.com
|
||
ADMIN_USERNAME=admin
|
||
ADMIN_NAME=Admin User
|
||
ADMIN_ROLE_LOCK=true
|
||
|
||
HOST_ONLY_ALLOWLIST=127.0.0.1,::1
|
||
|
||
RATE_LIMIT_WINDOW_MS=900000
|
||
RATE_LIMIT_MAX=100
|
||
|
||
LOGIN_RATE_LIMIT_WINDOW_MS=60000
|
||
LOGIN_RATE_LIMIT_MAX=5
|
||
```
|
||
|
||
## Endpointler
|
||
|
||
- `GET /health` — `{ ok: true, time }`
|
||
- `POST /auth/register` — body `{ email, name, username, password }`
|
||
- `POST /auth/login` — body `{ emailOrUsername, password }`
|
||
- `POST /auth/logout` — cookie tabanlı JWT siler
|
||
- `GET /auth/me` — aktif kullanıcıyı döndürür
|
||
- `POST /auth/refresh` — yeni JWT üretir (iskelet)
|
||
- `POST /meta/scrape` — body `{ url }`, destekli domainler: `netflix.com`, `primevideo.com` (yalnızca `role: user`)
|
||
- Başarılı sorgu Supabase `media_data` tablosuna kaydedilir: `media_id, media_name, year, type(movie/tv), thumbnail_url, info, genre, media_provider (netflix/primevideo)`
|
||
|
||
### Postman örnekleri
|
||
|
||
**Register**
|
||
```
|
||
POST {{baseUrl}}/auth/register
|
||
Content-Type: application/json
|
||
Body:
|
||
{
|
||
"email": "user@example.com",
|
||
"name": "Test User",
|
||
"username": "testuser",
|
||
"password": "password123"
|
||
}
|
||
```
|
||
**200**:
|
||
```
|
||
{
|
||
"user": {
|
||
"id": "...",
|
||
"email": "user@example.com",
|
||
"name": "Test User",
|
||
"username": "testuser",
|
||
"roleEffective": "user"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Login**
|
||
```
|
||
POST {{baseUrl}}/auth/login
|
||
Content-Type: application/json
|
||
Body:
|
||
{
|
||
"emailOrUsername": "testuser",
|
||
"password": "password123"
|
||
}
|
||
```
|
||
**200**:
|
||
```
|
||
{
|
||
"user": {
|
||
"id": "...",
|
||
"email": "user@example.com",
|
||
"name": "Test User",
|
||
"username": "testuser",
|
||
"roleEffective": "user"
|
||
}
|
||
}
|
||
```
|
||
JWT otomatik olarak `Cookie: sid=<token>` olarak set edilir.
|
||
|
||
**Me**
|
||
```
|
||
GET {{baseUrl}}/auth/me
|
||
Cookie: sid=<token>
|
||
```
|
||
**200**:
|
||
```
|
||
{
|
||
"user": {
|
||
"id": "...",
|
||
"email": "...",
|
||
"username": "...",
|
||
"roleEffective": "user"
|
||
}
|
||
}
|
||
```
|
||
|
||
**Logout**
|
||
```
|
||
POST {{baseUrl}}/auth/logout
|
||
Cookie: sid=<token>
|
||
```
|
||
**200**:
|
||
```
|
||
{ "message": "Çıkış yapıldı" }
|
||
```
|
||
|
||
**Meta scrape (Netflix/Prime)**
|
||
```
|
||
POST {{baseUrl}}/meta/scrape
|
||
Content-Type: application/json
|
||
Cookie: sid=<token> # roleEffective=user
|
||
Body:
|
||
{ "url": "https://www.netflix.com/tr/title/81507921" }
|
||
```
|
||
Prime örneği:
|
||
```
|
||
{ "url": "https://www.primevideo.com/-/tr/detail/0NHIN3TGAI9L7VZ45RS52RHUPL/ref=share_ios_movie" }
|
||
```
|
||
**200** (örnek):
|
||
```
|
||
{
|
||
"provider": "netflix",
|
||
"type": "movie",
|
||
"media": {
|
||
"url": "https://www.netflix.com/title/82123114",
|
||
"id": "82123114",
|
||
"name": "ONE SHOT with Ed Sheeran",
|
||
"year": "2025",
|
||
"seasons": null,
|
||
"thumbnail": "...",
|
||
"info": "...",
|
||
"genre": "..."
|
||
},
|
||
"saved": {
|
||
"media_id": "82123114",
|
||
"media_name": "ONE SHOT with Ed Sheeran",
|
||
"year": "2025",
|
||
"type": "movie",
|
||
"thumbnail_url": "...",
|
||
"info": "...",
|
||
"genre": "...",
|
||
"media_provider": "netflix",
|
||
"created_at": "..."
|
||
}
|
||
}
|
||
```
|
||
|
||
Hata çıktıları tek tip:
|
||
```
|
||
{
|
||
"error": { "code": "ERROR_CODE", "message": "Açıklama", "details": [...] }
|
||
}
|
||
```
|
||
|
||
## Supabase şeması (SQL)
|
||
|
||
```sql
|
||
create extension if not exists "pgcrypto";
|
||
|
||
create table if not exists public.users (
|
||
id uuid primary key default gen_random_uuid(),
|
||
email text unique not null,
|
||
name text not null,
|
||
username text unique not null,
|
||
password_hash text not null,
|
||
role text not null default 'user' check (role in ('user','admin')),
|
||
created_at timestamp with time zone default now()
|
||
);
|
||
|
||
create table if not exists public.media_data (
|
||
media_id varchar,
|
||
media_name text,
|
||
year numeric,
|
||
type text,
|
||
thumbnail_url varchar,
|
||
info text,
|
||
genre text,
|
||
media_provider text,
|
||
created_at timestamp with time zone default now()
|
||
);
|
||
```
|
||
|
||
> Admin rol kilidi: Sadece `.env` içindeki `ADMIN_EMAIL` ve `ADMIN_USERNAME` ile birebir eşleşen kullanıcı API tarafından `admin` kabul edilir. Supabase üzerinde rol elle değiştirilse bile etkisizdir.
|
||
|
||
## Güvenlik notları
|
||
|
||
- `hostOnly` middleware ve CORS sadece allowlist’teki host/ip erişimine izin verir.
|
||
- Global rate limit + login özel rate limit aktiftir.
|
||
- JWT httpOnly cookie olarak set edilir; Redis içinde `session:{jti}` anahtarıyla oturum metadata tutulur.
|
||
|
||
## Socket.io
|
||
|
||
- Handshake sırasında cookie’deki JWT doğrulanır, Redis oturum kontrol edilir.
|
||
- Başarılı bağlantıda `welcome` eventi döner.
|
||
- Örnek ping/pong:
|
||
- Client: `socket.emit('ping')`
|
||
- Server cevabı: `pong` içinde zaman damgası.
|
||
|
||
## Docker notları
|
||
|
||
- `docker-compose.yml` içinde `api` servisi `redis` servisine bağımlıdır.
|
||
- Geliştirme için kod klasörü container içine mount edilerek canlı yenileme (`npm run dev`) sağlanır.
|
||
- Sağlık kontrolü: container içinde `GET /health` çağrısı ile yapılır.
|
||
|
||
## Yapılandırılabilir noktalar
|
||
|
||
- Rate limit penceresi ve max değerleri `.env` ile ayarlanır.
|
||
- `HOST_ONLY_ALLOWLIST` ile izin verilen host/ip listesi yönetilir.
|
||
- `ADMIN_ROLE_LOCK` kapatılmadıkça tek admin `.env`’deki kullanıcıdır.
|
||
|
||
## TODO / Genişletme fikirleri
|
||
|
||
- Refresh token için ayrı bir anahtar ve whitelist mekanizması.
|
||
- Audit logları (morgan loglarını dosyaya yazma).
|
||
- Supabase RPC veya policy’lerle ek güvenlik.
|
||
- Testler (Jest/Supertest) ve CI pipeline’ı.
|