Düşüncenin Haritası — A Map of Thought
İnsanların felsefeyi interaktif bir bilgi grafiği üzerinden keşfetmesini sağlayan iki dilli (TR/EN) bir web platformu. Bir ansiklopedi değildir — bir düşünce navigasyon aracıdır.
286 düğüm (50 filozof, 78 kavram, 22 akım, 81 eser, 50 soru, 5 dönem) ve 692 ilişki içeren canlı bir bilgi grafiği üzerine kurulmuştur.
| Katman | Teknoloji |
|---|---|
| Framework | Next.js 16 (App Router, Turbopack) |
| Dil | TypeScript (strict) |
| Stil | Tailwind CSS v4 (@theme) |
| Graph | @xyflow/react (React Flow) + @dagrejs/dagre layout |
| Animasyon | Framer Motion |
| State | Zustand |
| i18n | next-intl (TR varsayılan, EN) |
| Arama | Fuse.js (client-side fuzzy search) |
| Veritabanı | Supabase (PostgreSQL) |
| ORM | Prisma 6 |
| Auth | Supabase Auth (yalnızca admin) |
| Hosting | Vercel |
src/
├── app/
│ ├── [locale]/ # i18n public site
│ │ ├── page.tsx # Ana sayfa (hero + arama + Düşünceye Dal)
│ │ ├── explore/ # /explore — interaktif graph
│ │ ├── timeline/ # /timeline — yatay zaman çizelgesi
│ │ ├── {philosopher,concept,school,work,question,period}/[slug]/
│ │ └── about/
│ ├── admin/ # Korumalı admin paneli
│ │ ├── login/
│ │ └── (protected)/{nodes,edges}/
│ ├── api/
│ │ ├── graph/ # Trim'lenmiş graph payload (~82 KB)
│ │ ├── search-index/ # Sadece isim+slug+tip (~26 KB)
│ │ └── random-question/
│ ├── robots.ts
│ └── sitemap.ts # 580+ URL, hreflang dahil
├── components/{graph,detail,home,layout,search,timeline,ui}/
├── lib/
│ ├── db.ts # Prisma client singleton
│ ├── supabase-{server,browser}.ts
│ ├── queries.ts # getNodeBySlug (React.cache)
│ ├── seo.ts # buildNodeMetadata + buildNodeJsonLd
│ ├── graph-layout.ts # dagre wrapper
│ └── timeline.ts # lane-packing algoritması
├── store/{graphStore,filterStore}.ts
├── i18n/{routing,navigation,request}.ts
├── messages/{tr,en}.json
├── proxy.ts # next-intl + Supabase auth gate
└── types/{node,edge,graph}.ts
prisma/
└── schema.prisma # nodes + edges (UUID PK, JSONB metadata)
seed/
├── {philosophers,concepts,schools,works,questions,periods}.json
├── edges.json
└── seed.ts # tsx ile çalışır
npm installNOUS, kendi Supabase projeni gerektirir. Hazır bir DB ile gelmez. Aşağıdaki adımlar tamamen ücretsiz tier üzerinde tamamlanır.
- supabase.com → Start your project → GitHub ile giriş
- New Project:
- Name:
nous(veya istediğin) - Database Password: güçlü bir şifre üret, kaydet (sonra .env'e koyacaksın)
- Region: kullanıcılarına yakın olanı seç (TR için
eu-central-1/ Frankfurt önerilir — Tokyo gibi uzak region'lar latency'yi 10×'lar) - Pricing Plan: Free (yeterli — 500 MB DB, 2 GB bandwidth)
- Name:
- Proje oluşması ~2 dk sürer.
Supabase Dashboard'da projene gir, Project Settings (alt sol gear ikonu):
Settings → API:
Project URL→ kopyala (örn.https://abcd1234.supabase.co) — buNEXT_PUBLIC_SUPABASE_URLolacakProject API keys → anon / public→ kopyala (sb_publishable_...ile başlar) — buNEXT_PUBLIC_SUPABASE_ANON_KEYolacak
Settings → Database → Connection string:
- Tab "URI" → "Connection pooling" alt menüsünü göster
- Mode: Transaction seçili olsun
- String'i kopyala (örn.
postgresql://postgres.abcd1234:[YOUR-PASSWORD]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres) [YOUR-PASSWORD]yerine 2.1'de kaydettiğin şifreyi koy- Sonuna
?pgbouncer=true&connection_limit=1&pool_timeout=30ekle → buDATABASE_URLolacak - Aynı string'i
:6543/postgresyerine:5432/postgresolacak şekilde kopyala, hiçbir parametre eklemeden → buDIRECT_URLolacak (Prisma migrate'in kullandığı non-pooled bağlantı)
cp .env.example .env.local.env.local'i aç, 2.2'de topladıklarınla doldur:
DATABASE_URL="postgresql://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1&pool_timeout=30"
DIRECT_URL="postgresql://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres"
NEXT_PUBLIC_SITE_URL="http://localhost:3000"
NEXT_PUBLIC_SUPABASE_URL="https://<ref>.supabase.co"
NEXT_PUBLIC_SUPABASE_ANON_KEY="sb_publishable_..."
⚠️ .env.localasla git'e push edilmez —.gitignoreengelliyor. Yine de push'tan öncegit statusile kontrol et.
connection_limit=1vepool_timeout=30kritik: pgbouncer üzerinden Prisma havuzunu doğru ayarlar. Build sırasında 200+ paralel sayfa rendering connection limit'ini aşmasın.
SUPABASE_SERVICE_ROLE_KEYgerekmez — kod hiç kullanmıyor. RLS bypass etmek istemiyoruz; admin yazımları zatenDATABASE_URLüzerinden Prisma ile geçiyor.
Prisma şemasını gönder:
npm run db:generate # Prisma client üret
npm run db:push # nodes + edges tablolarını oluşturRLS'i mutlaka aç — anon key browser'a expose ediliyor; RLS yoksa herkes yazabilir:
Supabase Dashboard → Authentication → Policies → her iki tablo (nodes, edges) için:
- Enable RLS butonuna bas
- "New Policy" → "From scratch" seç
- Aşağıdaki politikayı uygula (sadece SELECT izni
anonrolüne, INSERT/UPDATE/DELETE hiç kimseye):
-- nodes tablosu için
CREATE POLICY "Public read access" ON nodes
FOR SELECT USING (true);
-- edges tablosu için
CREATE POLICY "Public read access" ON edges
FOR SELECT USING (true);Admin yazımları RLS'i bypass eden Postgres connection string üzerinden geçer — Supabase Auth'la değil. Bu yüzden
anonrolüne yazma izni vermene gerek YOK.
Supabase Dashboard → Authentication → Users → Add user → Create new user:
- Email: kendi email'in
- Password: güçlü bir şifre
- ✅ Auto Confirm User işaretle (email doğrulama gönderilmez)
- Create user
Bu hesapla /admin/login üzerinden gireceksin. Self-registration yok; ekstra admin gerekiyorsa yine bu yoldan eklersin.
npm run db:seed # 286 node + 692 edge yükle (idempotent upsert)Repo'da 50 filozof, 78 kavram, 22 akım, 81 eser, 50 soru, 5 dönem ve 692 ilişkilik bir başlangıç dataset'i var (
seed/*.json). İdempotent — script'i tekrar çalıştırırsan veriyi düplike etmez, sadece günceller.
Public site DB'ye runtime'da gitmiyor — data/ ve public/data/ altındaki statik JSON dosyalarından okuyor. İlk dev başlangıcından önce bir kez:
npm run cache-data # DB'den çek, JSON dosyalarını üret (~5-20 sn)Bu adım atlanırsa public sayfaları "missing data/graph.json — run npm run cache-data" hatasıyla açılmaz. Admin paneli her zaman DB'ye direkt yazıyor; cache yenileme admin'in /admin/cache sayfasından da tetiklenebilir.
npm run dev→ http://localhost:3000/tr (TR varsayılan) veya http://localhost:3000/en. Admin paneli: http://localhost:3000/admin.
| Komut | Yapar |
|---|---|
npm run dev |
Dev server (Turbopack) |
npm run build |
Production build (otomatik olarak cache-data çalıştırır) |
npm run start |
Production server |
npm run typecheck |
tsc --noEmit |
npm run cache-data |
DB'den data/*.json ve public/data/*.json üret |
npm run db:generate |
Prisma client üret |
npm run db:push |
Şemayı DB'ye gönder |
npm run db:pull |
Şemayı DB'den çek |
npm run db:seed |
seed/seed.ts çalıştır |
npm run db:studio |
Prisma Studio aç |
İki tablo, polimorfik:
- nodes —
typeile ayrımlı (philosopher / concept / school / work / question / period); ortak alanlar (name_tr/en,description_short_tr/en), tipe-özel alanlarmetadataJSONB içinde - edges — yönlü, 21 tip (
influenced_by,teacher_of,authored,developed,coined,opposes,central_to,active_during,addresses,explores,discusses, …)
Şema detayları: prisma/schema.prisma.
- HTML/body root layout'ta — public site (
[locale]/) ve admin (admin/) tek bir HTML köküne paralel oturur - Admin gate iki katmanlı:
proxy.tsmiddleware'indegetUser()+(protected)/layout.tsxiçinde defence-in-depthredirect() - Graph layout cache: dagre tüm 286 node için
useMemo([data])ile bir kez hesaplanır, filter değişimlerinde sadecehiddenflag'i toggle olur (~150 ms → ~10 ms iyileştirme) - Detail sayfa cache:
getNodeBySlugReact.cache()ile sarılı — page +generateMetadataaynı request'te tek sorgu - Search index — Fuse.js'i besleyen endpoint sadece
name_tr/en + slug + type + importancedöner (~26 KB) - JSON-LD — her detay sayfası tipine göre
Person/Book/DefinedTermschema.org çıkarır - Sitemap — dinamik, hreflang
tr↔en↔x-defaultüçlüsünü her URL için emit eder
Deploy etmeden veya repo'yu public yapmadan önce:
.env.localgit'e kesinlikle gitmemeli —.gitignorezaten engelliyor; yine degit ls-files | grep .envile doğrula..env.exampleplaceholder'lar içerir, gerçek değer yok.- Supabase RLS açık olmalı: kod runtime'da
anonkey kullanıyor (browser'a expose ediliyor).nodesveedgestablolarında RLS yoksa veyaanonrolüne yazma izni varsa, herkes admin'i bypass edip içerik değiştirebilir.- Supabase Dashboard → Authentication → Policies → her iki tablo için "Enable RLS" + sadece SELECT izni ver. INSERT/UPDATE/DELETE'i authenticated role'e bile vermene gerek yok (admin yazımları zaten
proxy.tsarkasından server-side Prisma ile geçiyor, ki oDATABASE_URL(Postgres connection string) üzerinden bağlanır —anonkey'i kullanmaz).
- Supabase Dashboard → Authentication → Policies → her iki tablo için "Enable RLS" + sadece SELECT izni ver. INSERT/UPDATE/DELETE'i authenticated role'e bile vermene gerek yok (admin yazımları zaten
SUPABASE_SERVICE_ROLE_KEYkullanılmıyor: Koddaservice_roleçağrısı yok. Env'inden kaldırabilirsin; ekleyeceksen yalnızca server-side (aslaNEXT_PUBLIC_*prefix'i ile değil).- Admin gate iki katmanlı:
proxy.ts/admin/*'ı kapı bekçisi,(protected)/layout.tsxdefence-in-depthredirect(), her server actionrequireUser()çağırır. - Open-redirect koruması: Login formunda
?next=parametresi sadece/adminprefix'ini kabul eder (safeNext()). - XSS hardening:
JsonLdkomponenti<,>,&karakterlerini unicode escape eder — admin malicious içerik girerse bile script enjeksiyonu olmaz. - Güvenlik header'ları:
next.config.tsher response'aX-Frame-Options: DENY,X-Content-Type-Options: nosniff,HSTS,Permissions-Policyekler. CSP yok (Next.js'in inline script bootstrap'ı ile çakışıyor — sonraki iş). - robots.txt:
/adminve/apicrawlerlardan gizli. - Service role exposure check:
git grep -i service_rolepush öncesi boş çıkmalı.
- Vercel'e bağla — repo otomatik algılanır
- Project Settings → Environment Variables —
.env.local'daki tüm değişkenleri ekle NEXT_PUBLIC_SITE_URL'ı production domain'e çevir (ör.https://nous.app)- (Opsiyonel ama önerilen) Project Settings → Git → Deploy Hooks → "Cache regen" adıyla bir hook oluştur, URL'i kopyala, Environment Variables'a
VERCEL_DEPLOY_HOOK_URLolarak ekle. Bu, admin panelindeki "Cache'i yenile" butonunun production'da çalışmasını sağlar. - Deploy
Build adımı npx prisma generate çalıştırmak için prisma zaten dependencies altında. prebuild hook'u cache-data'yı tetikler — her build DB'den taze veri çeker.
Public site runtime'da DB'ye gitmiyor. Tüm sayfalar build-time'da üretilen statik JSON dosyalarından okuyor. Bu Türkiye-Tokyo latency'sinin (5-17 sn) etkisini sıfırlıyor.
Cache dosyaları:
| Dosya | Boyut | Kullanan |
|---|---|---|
data/graph.json |
~380 KB | /explore, /timeline, detail sayfaları (server-only) |
data/stats.json |
~220 B | /, /about (counts) |
public/data/search-index.json |
~44 KB | SearchBar (CDN'den fetch) |
public/data/questions.json |
~7 KB | DiveButton (CDN'den fetch) |
Yenileme:
| Senaryo | Yapılacak |
|---|---|
| Yeni dev makinesi | npm run cache-data (ilk başlangıçtan önce bir kez) |
| Local geliştirme sırasında DB değişti | Admin → /admin/cache → "Cache'i yenile" (script local çalışır) |
| Production'da admin yazımı sonrası | Admin → /admin/cache → "Cache'i yenile" (Vercel deploy hook tetikler, ~1-2 dk) |
| Otomatik tazeleme | Vercel Cron'a /admin/cache action'ını günlük çağıran job ekle |
Hata davranışı: cache-data DB'ye ulaşamadığında ve disk'te eski cache varsa → eski cache'i koruyup exit 0. Cache yoksa exit 1 (build kırılır). Bu, geçici Supabase outage'lerinde production'ı düşürmemeyi garantiler.
seed/<type>.jsondosyasına yeni node ekleseed/edges.json'a en az 1 ilişki ekle (orphan kalmasın)npm run db:seedçalıştır (upsert — idempotent)
/admin/login— Supabase Auth ile giriş- Düğümler sekmesi → "+ Yeni düğüm" → form
- İlişkiler sekmesi → "+ Yeni ilişki" → source/target dropdown + tip
Orphan check admin dashboard'unda görünür.
İçerik açık kaynak felsefe literatüründen derlenmiştir. Kod MIT lisansı altındadır.