From c1c57c4f85b349aa1995939d6b668863aaed5f49 Mon Sep 17 00:00:00 2001 From: suphicakir Date: Fri, 16 Oct 2026 01:15:05 +0300 Subject: [PATCH] feat: add anonymous view counter with most-read sidebar - node:sqlite storage in $DATA_DIR/views.db (no native deps) - POST /api/views// counts once per visitor per 24h via daily-salted hash; IPs are never stored, bots are skipped - GET /api/views/top/ feeds the sidebar's featured list, falling back to frontmatter-featured and latest posts when unavailable Co-Authored-By: Claude Opus 5.5 --- PROJECT_MEMORY.md | 58 +++++++++++++++------------ src/components/blog/Sidebar.astro | 38 +++++++++++++++++- src/lib/posts.ts | 4 +- src/lib/views.ts | 66 +++++++++++++++++++++++++++++++ src/pages/api/views/[slug].ts | 16 ++++++++ src/pages/api/views/top.ts | 19 +++++++++ src/pages/blog/[slug].astro | 7 ++++ 7 files changed, 178 insertions(+), 30 deletions(-) create mode 100644 src/lib/views.ts create mode 100644 src/pages/api/views/[slug].ts create mode 100644 src/pages/api/views/top.ts diff --git a/PROJECT_MEMORY.md b/PROJECT_MEMORY.md index 6b99cd4..63af173 100644 --- a/PROJECT_MEMORY.md +++ b/PROJECT_MEMORY.md @@ -1,13 +1,13 @@ # PROJECT_MEMORY — ta1sph.info > Claude'un bu proje için kalıcı hafızası. Her görev sonunda güncellenir. Kısa ve güncel tutulur. -> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 4) +> Son güncelleme: 2026-10-16 · Güncelleyen: Claude (Siteyi Kur — Aşama 5) ## 1. Durum -- **Aşama:** 4 tamamlandı (blog altyapısı). Sıradaki: **Aşama 5 — Görüntülenme sayacı**. -- **Tamamlanan:** Aşama 0–4. Stil önizleme yalnızca dev'de: `http://localhost:4321/styleguide/`. -- **Açık işler:** Aşama 5–8. Sidebar "Öne Çıkan" şimdilik frontmatter `featured` (yoksa en yeni 3) → A5'te API ile doldurulacak (`ol[data-featured]`). Yasal sayfalar yok (A6). Favicon + `og-default.png` yok (A7); kapaksız yazılarda og:image yok. `LICENSE` dosyaları A6. 3 örnek yazı (`örnek` etiketli) kullanıcı isteyince silinecek. +- **Aşama:** 5 tamamlandı (görüntülenme sayacı). Sıradaki: **Aşama 6 — Hakkımda, İletişim, Yasal, 404**. +- **Tamamlanan:** Aşama 0–5. Stil önizleme yalnızca dev'de: `http://localhost:4321/styleguide/`. +- **Açık işler:** Aşama 5–8. Yasal sayfalar yok (A6). Favicon + `og-default.png` yok (A7); kapaksız yazılarda og:image yok. `LICENSE` dosyaları A6. 3 örnek yazı (`örnek` etiketli) kullanıcı isteyince silinecek. - **Bilinen sorunlar:** Arama (Pagefind) `astro dev`'de çalışmaz, yalnızca build sonrası. Kullanıcının elle başlattığı dev sunucusu yeni koleksiyonu görmüyorsa yeniden başlatılmalı. ## 2. Ortam @@ -36,6 +36,9 @@ - `src/pages/blog/[...page].astro` (/blog/, /blog/sayfa/n/) · `blog/[slug].astro` (detay + BlogPosting/BreadcrumbList JSON-LD, kod kopyala script'i) · `blog/kategori/[category]/[...page].astro` · `blog/etiket/[tag]/[...page].astro` · `src/pages/rss.xml.ts` - `src/components/blog/` — `BlogListing` (liste düzeni + sidebar), `PostCard` (row/stack, kapaksızsa kategori ikonu), `DateBadges`, `Pager`, `Sidebar`, `SearchBox` (Pagefind JS API, özel UI), `ShareBar`, `Toc`, `Breadcrumbs`, `DiyNotice` - `global.css` — `.article-prose` (prose token eşlemesi), Shiki çift tema, `.code-copy` +- `src/lib/views.ts` — `node:sqlite` sayaç (`$DATA_DIR/views.db`, varsayılan `./data`; Docker'da `/app/data` volume). Tablolar: `views(slug,count)`, `hits(hash,at)` 24 sa, `salts(day,salt)` günlük tuz. IP saklanmaz; `clientIp` önce `X-Forwarded-For`; `isBot` UA filtresi (headless dahil) +- `src/pages/api/views/[slug].ts` (POST, 204; bilinmeyen slug 404) · `src/pages/api/views/top.ts` (GET `?limit=1..10`, JSON `{slug,title,url,count}`, 5 dk cache) — ikisi `prerender = false` +- Yazı sayfası `sendBeacon('/api/views//')`; Sidebar script'i `/api/views/top/` ile `ol[data-featured]`'ı değiştirir, eksikleri build-time yedekle tamamlar - `src/dev/Styleguide.astro` — `astro.config.mjs`'deki `devRoutes` entegrasyonu ile sadece `astro dev`'de `/styleguide/` (`_` önekli sayfalar Astro'da rota olmaz) - `src/lib/slug.ts` — Türkçe karakter dönüştürücü `slugify` - `scripts/new-post.mjs` — `npm run new:post -- "Başlık"` → `src/content/blog//index.md` (draft: true); `slug.ts`'yi Node type-stripping ile import eder @@ -45,28 +48,30 @@ ## 4. Kararlar (ADR) -| Tarih | Karar | Gerekçe | -| ---------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | -| 2026-10-15 | Statik çıktı + `@astrojs/node` ile sadece `/api/views` dinamik | "Öne çıkan = en çok okunan" için sayaç gerekli; site geri kalanı statik ve hızlı | -| 2026-10-15 | Sayaç: SQLite (`better-sqlite3`), volume `/app/data` | Harici servis yok, ev sunucusunda basit ve kalıcı | -| 2026-10-15 | Arama: Pagefind | Statik, sunucusuz, Türkçe destekli | -| 2026-10-15 | Harita: Leaflet + OpenStreetMap | API anahtarı / ücret yok, KVKK dostu | -| 2026-10-15 | İkonlar: astro-icon + Tabler | Tutarlı çizgi seti, anten kulesi ve marka ikonları mevcut | -| 2026-10-15 | Lisans: Kod MIT, içerik CC BY-SA 4.0 | Kaynak gösterilerek serbest kullanım talebi | -| 2026-10-15 | Kanonik alan: apex `ta1sph.info`, www → 301 | SEO tekilleştirme | -| 2026-10-15 | Fontlar self-host (Fontsource: Rubik + Inter) | Performans + gizlilik | -| 2026-10-16 | Ek token'lar: `accent-fill`/`on-accent-fill`, `accent-text` (khaki'de fg'ye döner) | Ölçüm: ink/red 4.46, red-strong/khaki 3.85 → küçük metin AA'yı geçmiyordu. Kırmızı zeminli butonlar ≥19px bold | -| 2026-10-16 | Astro 7 Markdown = Sätteri; eklentiler `markdown.processor: satteri({ hastPlugins })` | remark/rehype eklentileri bu işlemcide çalışmaz | -| 2026-10-16 | Okuma süresi `post.body` kelime sayısından (remark eklentisi yok) | Liste sayfalarında `render()` gerekmez, Sätteri'de remark yok | -| 2026-10-16 | Sayfalama `[...page]` + `sayfa/` (Astro `paginate()` değil) | Spec URL'leri `/blog/sayfa/n/`; tek dosyada 1. sayfa + diğerleri | -| 2026-10-16 | Arama UI'ı kendi bileşenimiz (Pagefind JS API), indeks yalnızca `data-pagefind-body` (yazı gövdeleri) | Tasarımla uyum, Türkçe metinler JSON'da | -| 2026-10-16 | Shiki `github-*-high-contrast`, zemin `--surface` | Kod renkleri AA kontrastı | -| 2026-10-16 | Dekoratif dev rakamlar CSS `::before` içeriği | axe/Lighthouse aria-hidden metinde bile kontrast hatası veriyordu | -| 2026-10-16 | Kuruluş/site tanıtımları yalnızca sitelerin kendi ifadelerinden | Uydurma bilgi yok; Telegram kartlarında açıklama yok (doğrulanmadı) | -| 2026-10-16 | Mobil menü native `popover` | Escape/odak/light-dismiss JS'siz | -| 2026-10-16 | Pagefind CLI postbuild (`astro-pagefind` yok) | Daha az bağımlılık; node adapter çıktısı `dist/client` | -| 2026-10-16 | Skill repoya kopyalanmadı, kullanıcı düzeyinde | Çift kopya senkron riski | -| 2026-10-15 | Locator KN41KB99HW → 41.08307 N, 28.91094 E | Harita merkezi | +| Tarih | Karar | Gerekçe | +| ---------- | ----------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| 2026-10-15 | Statik çıktı + `@astrojs/node` ile sadece `/api/views` dinamik | "Öne çıkan = en çok okunan" için sayaç gerekli; site geri kalanı statik ve hızlı | +| 2026-10-15 | Sayaç: SQLite, volume `/app/data` (sürücü: aşağıdaki 2026-10-16 kararı) | Harici servis yok, ev sunucusunda basit ve kalıcı | +| 2026-10-15 | Arama: Pagefind | Statik, sunucusuz, Türkçe destekli | +| 2026-10-15 | Harita: Leaflet + OpenStreetMap | API anahtarı / ücret yok, KVKK dostu | +| 2026-10-15 | İkonlar: astro-icon + Tabler | Tutarlı çizgi seti, anten kulesi ve marka ikonları mevcut | +| 2026-10-15 | Lisans: Kod MIT, içerik CC BY-SA 4.0 | Kaynak gösterilerek serbest kullanım talebi | +| 2026-10-15 | Kanonik alan: apex `ta1sph.info`, www → 301 | SEO tekilleştirme | +| 2026-10-15 | Fontlar self-host (Fontsource: Rubik + Inter) | Performans + gizlilik | +| 2026-10-16 | Ek token'lar: `accent-fill`/`on-accent-fill`, `accent-text` (khaki'de fg'ye döner) | Ölçüm: ink/red 4.46, red-strong/khaki 3.85 → küçük metin AA'yı geçmiyordu. Kırmızı zeminli butonlar ≥19px bold | +| 2026-10-16 | SQLite sürücüsü `node:sqlite` (better-sqlite3 değil) — kullanıcı onayı | Native derleme yok, ek paket yok; Node 26'da uyarısız. Docker imajı Node ≥24 olmalı (A8'de kesinleşecek) | +| 2026-10-16 | Astro `checkOrigin` açık kalıyor | Tarayıcı `Sec-Fetch-Site: same-origin` gönderir; başka sitelerden sayaç şişirme engellenir. curl testlerinde bu başlık eklenmeli | +| 2026-10-16 | Astro 7 Markdown = Sätteri; eklentiler `markdown.processor: satteri({ hastPlugins })` | remark/rehype eklentileri bu işlemcide çalışmaz | +| 2026-10-16 | Okuma süresi `post.body` kelime sayısından (remark eklentisi yok) | Liste sayfalarında `render()` gerekmez, Sätteri'de remark yok | +| 2026-10-16 | Sayfalama `[...page]` + `sayfa/` (Astro `paginate()` değil) | Spec URL'leri `/blog/sayfa/n/`; tek dosyada 1. sayfa + diğerleri | +| 2026-10-16 | Arama UI'ı kendi bileşenimiz (Pagefind JS API), indeks yalnızca `data-pagefind-body` (yazı gövdeleri) | Tasarımla uyum, Türkçe metinler JSON'da | +| 2026-10-16 | Shiki `github-*-high-contrast`, zemin `--surface` | Kod renkleri AA kontrastı | +| 2026-10-16 | Dekoratif dev rakamlar CSS `::before` içeriği | axe/Lighthouse aria-hidden metinde bile kontrast hatası veriyordu | +| 2026-10-16 | Kuruluş/site tanıtımları yalnızca sitelerin kendi ifadelerinden | Uydurma bilgi yok; Telegram kartlarında açıklama yok (doğrulanmadı) | +| 2026-10-16 | Mobil menü native `popover` | Escape/odak/light-dismiss JS'siz | +| 2026-10-16 | Pagefind CLI postbuild (`astro-pagefind` yok) | Daha az bağımlılık; node adapter çıktısı `dist/client` | +| 2026-10-16 | Skill repoya kopyalanmadı, kullanıcı düzeyinde | Çift kopya senkron riski | +| 2026-10-15 | Locator KN41KB99HW → 41.08307 N, 28.91094 E | Harita merkezi | ## 5. İçerik Envanteri @@ -78,6 +83,7 @@ ## 6. Değişiklik Günlüğü (en yeni üstte) +- 2026-10-16 | Siteyi Kur A5 | Görüntülenme sayacı (node:sqlite, anonim, 24 sa tekil, bot filtresi), top API, sidebar öne çıkanlar API + yedek; uçtan uca test | `src/lib/views.ts`, `src/pages/api/views/`, `src/components/blog/Sidebar.astro`, `src/pages/blog/[slug].astro`, `src/lib/posts.ts` - 2026-10-16 | Siteyi Kur A4 | Blog: koleksiyon, liste/kategori/etiket arşivleri + sayfalama, detay (paylaş, TOC, DIY, kod kopyala, lisans satırı), sidebar (Pagefind arama), RSS, ana sayfa son yazılar, 3 örnek yazı; axe temiz | `src/content*`, `src/pages/blog/`, `src/pages/rss.xml.ts`, `src/components/blog/`, `src/lib/{posts,dates,markdown-plugins}`, `src/data/{blog,categories}.json`, `astro.config.mjs`, `global.css` - 2026-10-16 | Siteyi Kur A3 | Ana sayfa: hero, 4 kuruluş, Telegram, son yazılar (boş durum), site tanıtımları; axe temiz | `src/data/home.json`, `src/components/home/`, `src/components/Section.astro`, `src/pages/index.astro`, `src/lib/data.ts` - 2026-10-16 | Siteyi Kur A2 | Token'lar, tema anahtarı, SEO bileşeni, layout, header/footer, temel bileşenler, site/social JSON, dev styleguide; axe temiz (2 tema) | `src/styles/`, `src/components/`, `src/layouts/`, `src/data/`, `src/lib/data.ts`, `src/dev/`, `astro.config.mjs` diff --git a/src/components/blog/Sidebar.astro b/src/components/blog/Sidebar.astro index 0593728..cbc2867 100644 --- a/src/components/blog/Sidebar.astro +++ b/src/components/blog/Sidebar.astro @@ -71,9 +71,9 @@ const title = 'font-display mb-4 flex items-center gap-3 text-sm font-bold track {blog.sidebar.featured} -
    +
      {featured.map((post, i) => ( -
    1. +