diff --git a/src/data/equinoxHaber.ts b/src/data/equinoxHaber.ts index 35409bf..8d081f8 100644 --- a/src/data/equinoxHaber.ts +++ b/src/data/equinoxHaber.ts @@ -1,6 +1,6 @@ import type { CollectionEntry } from 'astro:content'; import { getNewsHomeHref, getNewsStreamPageHref } from './newsSite'; -import { getPublished } from '#news-source'; +import { getPublished, getPublishedCount } from '#news-source'; /** Ana sayfanın öne çıkan panelini besleyen havuz. */ export const EQUINOX_HABER_PAGE_SIZE = 20; @@ -16,6 +16,15 @@ export const NEWS_CATEGORIES = ['Siyaset', 'Ekonomi', 'Teknoloji', 'Bilim'] as c export type NewsCategory = (typeof NEWS_CATEGORIES)[number]; export type EquinoxHaberEntry = CollectionEntry<'equinoxHaber'>; +export interface PublishedNewsOptions { + limit?: number; + offset?: number; + category?: string; + since?: Date | string; + includeTags?: boolean; + includeSources?: boolean; +} + /** * Yayımlanmış haberler, yeniden eskiye. * @@ -29,8 +38,12 @@ export type EquinoxHaberEntry = CollectionEntry<'equinoxHaber'>; * diğeri koleksiyondan okusaydı yeni yayımlanan bir haber kendi adresinde * görünür ama listelerde görünmezdi. */ -export async function getPublishedEquinoxHaber() { - return getPublished(); +export async function getPublishedEquinoxHaber(options: PublishedNewsOptions = {}) { + return getPublished(options); +} + +export async function getPublishedEquinoxHaberCount() { + return getPublishedCount(); } export function getEquinoxHaberCategories() { diff --git a/src/data/equinoxHaberD1.ts b/src/data/equinoxHaberD1.ts index 5bd5c65..a6f1cd0 100644 --- a/src/data/equinoxHaberD1.ts +++ b/src/data/equinoxHaberD1.ts @@ -17,6 +17,16 @@ export interface NewsSource { name: string; url: string } +export interface PublishedD1Options { + withBody?: boolean; + limit?: number; + offset?: number; + category?: string; + since?: Date | string; + includeTags?: boolean; + includeSources?: boolean; +} + export interface D1NewsEntry { id: string; body: string; @@ -93,6 +103,43 @@ function assemble( })); } +const ARTICLE_META_COLUMNS = `slug, title, description, category, author, + '' AS body_md, '' AS body_html, + hero_image, hero_alt, pub_date, updated_date, is_draft, breaking, editor_pick`; + +function positiveInteger(value?: number) { + if (value === undefined) return undefined; + if (!Number.isFinite(value)) return undefined; + const normalized = Math.trunc(value); + return normalized > 0 ? normalized : undefined; +} + +function nonNegativeInteger(value?: number) { + if (value === undefined || !Number.isFinite(value)) return 0; + return Math.max(0, Math.trunc(value)); +} + +function isoDate(value: Date | string) { + return value instanceof Date ? value.toISOString() : new Date(value).toISOString(); +} + +async function readRelations( + db: D1Database, + slugs: string[], + sqlForPlaceholders: (placeholders: string) => string, +): Promise { + const rows: T[] = []; + /* D1/SQLite bağ parametresi sınırına yaklaşmamak için büyük arşiv sorgularını + * küçük parçalar halinde yürüt. Normal sayfalarda bu döngü tek turdur. */ + for (let index = 0; index < slugs.length; index += 80) { + const chunk = slugs.slice(index, index + 80); + const placeholders = chunk.map(() => '?').join(', '); + const result = await db.prepare(sqlForPlaceholders(placeholders)).bind(...chunk).all(); + rows.push(...(result.results ?? [])); + } + return rows; +} + /** Yayımlanmış haberler, yeniden eskiye. * * Sıralama `ORDER BY` ile veritabanında yapılıyor; koleksiyon tarafındaki @@ -107,23 +154,179 @@ function assemble( * `withBody` yalnız RSS için var ve orada da sınırlı sayıda haber isteniyor. */ export async function getPublishedFromD1( db: D1Database, - options: { withBody?: boolean; limit?: number } = {}, + options: PublishedD1Options = {}, ): Promise { const bodyColumns = options.withBody ? "body_md, body_html," : "'' AS body_md, '' AS body_html,"; - const limit = options.limit ? ` LIMIT ${Number(options.limit)}` : ""; + const where = ['is_draft = 0']; + const bindings: unknown[] = []; + + if (options.category) { + where.push('category = ?'); + bindings.push(options.category); + } + if (options.since) { + where.push('pub_date >= ?'); + bindings.push(isoDate(options.since)); + } + + const limit = positiveInteger(options.limit); + const offset = nonNegativeInteger(options.offset); + const pagination = limit + ? ` LIMIT ${limit}${offset ? ` OFFSET ${offset}` : ''}` + : offset + ? ` LIMIT -1 OFFSET ${offset}` + : ''; + + const articles = await db.prepare( + `SELECT slug, title, description, category, author, ${bodyColumns} + hero_image, hero_alt, pub_date, updated_date, is_draft, breaking, editor_pick + FROM articles + WHERE ${where.join(' AND ')} + ORDER BY pub_date DESC, slug ASC${pagination}`, + ).bind(...bindings).all(); + + const articleRows = articles.results ?? []; + const slugs = articleRows.map((row) => row.slug); + if (slugs.length === 0) return []; + + const includeTags = options.includeTags ?? true; + const includeSources = options.includeSources ?? true; + const [tags, sources] = await Promise.all([ + includeTags + ? readRelations<{ slug: string; tag: string }>( + db, + slugs, + (placeholders) => `SELECT slug, tag FROM article_tags WHERE slug IN (${placeholders}) ORDER BY slug, position`, + ) + : Promise.resolve([]), + includeSources + ? readRelations<{ slug: string; name: string; url: string }>( + db, + slugs, + (placeholders) => `SELECT slug, name, url FROM article_sources WHERE slug IN (${placeholders}) ORDER BY slug, position`, + ) + : Promise.resolve([]), + ]); - const [articles, tags, sources] = await Promise.all([ + return assemble(articleRows, tags, sources); +} + +export async function getPublishedCountFromD1(db: D1Database): Promise { + const row = await db.prepare( + 'SELECT COUNT(*) AS total FROM articles WHERE is_draft = 0', + ).first<{ total: number }>(); + return Number(row?.total ?? 0); +} + +export interface D1ArticlePageData { + entry: D1NewsEntry; + body: string; + bodyHtml: string | null; + nextEntry?: D1NewsEntry; + prevEntry?: D1NewsEntry; + relatedEntries: D1NewsEntry[]; +} + +/** Haber detay sayfası için bütün arşivi çekmeden gereken küçük çalışma seti. + * + * Eski yol her tekil haber isteğinde ~4-7 bin D1 satırı okuyordu çünkü ilgili + * haberleri JS'te hesaplamak için tüm haberler + tüm etiketler + tüm kaynaklar + * belleğe alınıyordu. Burada yalnız mevcut haber, komşuları ve üç ilgili aday + * okunur. Botların eski haber arşivini taraması bu yüzden günlük kotayı artık + * doğrusal biçimde eritmez. */ +export async function getArticlePageFromD1( + db: D1Database, + slug: string, +): Promise { + const current = await db.prepare( + `SELECT slug, title, description, category, author, body_md, body_html, + hero_image, hero_alt, pub_date, updated_date, is_draft, breaking, editor_pick + FROM articles + WHERE slug = ? AND is_draft = 0`, + ).bind(slug).first(); + if (!current) return null; + + const [tagsResult, sourcesResult, newer, older] = await Promise.all([ + db.prepare('SELECT slug, tag FROM article_tags WHERE slug = ? ORDER BY position') + .bind(slug).all<{ slug: string; tag: string }>(), + db.prepare('SELECT slug, name, url FROM article_sources WHERE slug = ? ORDER BY position') + .bind(slug).all<{ slug: string; name: string; url: string }>(), + db.prepare( + `SELECT ${ARTICLE_META_COLUMNS} + FROM articles + WHERE is_draft = 0 + AND (pub_date > ? OR (pub_date = ? AND slug < ?)) + ORDER BY pub_date ASC, slug DESC + LIMIT 1`, + ).bind(current.pub_date, current.pub_date, current.slug).first(), db.prepare( - `SELECT slug, title, description, category, author, ${bodyColumns} - hero_image, hero_alt, pub_date, updated_date, is_draft, breaking, editor_pick - FROM articles WHERE is_draft = 0 ORDER BY pub_date DESC${limit}`, - ).all(), - db.prepare("SELECT slug, tag FROM article_tags ORDER BY slug, position").all<{ slug: string; tag: string }>(), - db.prepare("SELECT slug, name, url FROM article_sources ORDER BY slug, position") - .all<{ slug: string; name: string; url: string }>(), + `SELECT ${ARTICLE_META_COLUMNS} + FROM articles + WHERE is_draft = 0 + AND (pub_date < ? OR (pub_date = ? AND slug > ?)) + ORDER BY pub_date DESC, slug ASC + LIMIT 1`, + ).bind(current.pub_date, current.pub_date, current.slug).first(), ]); - return assemble(articles.results ?? [], tags.results ?? [], sources.results ?? []); + const currentTags = (tagsResult.results ?? []).map((row) => row.tag); + let relatedWhere = 'a.category = ?'; + let scoreExpression = '6'; + let relatedBindings: unknown[] = [slug, current.category]; + + if (currentTags.length > 0) { + const placeholders = currentTags.map(() => '?').join(', '); + relatedWhere = `(a.category = ? OR a.slug IN ( + SELECT slug FROM article_tags WHERE tag IN (${placeholders}) + ))`; + scoreExpression = `(CASE WHEN a.category = ? THEN 6 ELSE 0 END) + 3 * ( + SELECT COUNT(*) FROM article_tags matched + WHERE matched.slug = a.slug AND matched.tag IN (${placeholders}) + )`; + /* SELECT içindeki yer tutucular WHERE'dekilerden önce bağlanır. */ + relatedBindings = [current.category, ...currentTags, slug, current.category, ...currentTags]; + } + + const related = await db.prepare( + `SELECT ${ARTICLE_META_COLUMNS}, ${scoreExpression} AS relation_score + FROM articles a + WHERE a.is_draft = 0 + AND a.slug <> ? + AND ${relatedWhere} + ORDER BY relation_score DESC, a.pub_date DESC, a.slug ASC + LIMIT 3`, + ).bind(...relatedBindings).all(); + + let relatedRows = related.results ?? []; + if (relatedRows.length < 3) { + const excluded = [slug, ...relatedRows.map((row) => row.slug)]; + const placeholders = excluded.map(() => '?').join(', '); + const fallback = await db.prepare( + `SELECT ${ARTICLE_META_COLUMNS} + FROM articles + WHERE is_draft = 0 AND slug NOT IN (${placeholders}) + ORDER BY pub_date DESC, slug ASC + LIMIT ${3 - relatedRows.length}`, + ).bind(...excluded).all(); + relatedRows = [...relatedRows, ...(fallback.results ?? [])]; + } + + const [entry] = assemble( + [current], + tagsResult.results ?? [], + sourcesResult.results ?? [], + ); + const [nextEntry] = newer ? assemble([newer], [], []) : []; + const [prevEntry] = older ? assemble([older], [], []) : []; + + return { + entry, + body: current.body_md, + bodyHtml: current.body_html, + nextEntry, + prevEntry, + relatedEntries: assemble(relatedRows, [], []), + }; } /** Tek haber, gövdesiyle. diff --git a/src/data/newsSource.ts b/src/data/newsSource.ts index 02b689e..53878b6 100644 --- a/src/data/newsSource.ts +++ b/src/data/newsSource.ts @@ -13,10 +13,71 @@ import { getCollection, render, type CollectionEntry } from 'astro:content'; export type NewsEntry = CollectionEntry<'equinoxHaber'>; -export async function getPublished(): Promise { - return (await getCollection('equinoxHaber')) +export interface PublishedOptions { + limit?: number; + offset?: number; + category?: string; + since?: Date | string; + includeTags?: boolean; + includeSources?: boolean; +} + +export async function getPublished(options: PublishedOptions = {}): Promise { + let entries = (await getCollection('equinoxHaber')) .filter((entry) => !entry.data.isDraft) - .sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf()); + .sort((a, b) => { + const dateOrder = b.data.pubDate.valueOf() - a.data.pubDate.valueOf(); + return dateOrder || a.id.localeCompare(b.id); + }); + + if (options.category) { + entries = entries.filter((entry) => entry.data.category === options.category); + } + if (options.since) { + const since = options.since instanceof Date ? options.since : new Date(options.since); + entries = entries.filter((entry) => entry.data.pubDate.valueOf() >= since.valueOf()); + } + + const offset = Math.max(0, Math.trunc(options.offset ?? 0)); + const limit = options.limit && options.limit > 0 ? Math.trunc(options.limit) : undefined; + return limit === undefined ? entries.slice(offset) : entries.slice(offset, offset + limit); +} + +export async function getPublishedCount(): Promise { + return (await getPublished()).length; +} + +/** Statik derlemede D1 maliyeti yok; aynı sayfa verisini koleksiyon üzerinden + * kurup sunucu sürümüyle aynı şekli döndürür. */ +export async function getArticlePage(slug: string) { + const entries = await getPublished(); + const currentIndex = entries.findIndex((entry) => entry.id === slug); + if (currentIndex < 0) return null; + + const entry = entries[currentIndex]!; + const currentTags = new Set(entry.data.tags ?? []); + const relatedEntries = entries + .filter((item) => item.id !== entry.id) + .map((item, index) => { + const sharedTags = (item.data.tags ?? []).filter((tag: string) => currentTags.has(tag)).length; + const sameCategory = item.data.category === entry.data.category; + return { + entry: item, + score: (sameCategory ? 6 : 0) + sharedTags * 3 - index * 0.001, + }; + }) + .sort((a, b) => b.score - a.score) + .slice(0, 3) + .map((item) => item.entry); + + return { + entry, + body: entry.body ?? '', + bodyHtml: null, + nextEntry: entries[currentIndex - 1], + prevEntry: entries[currentIndex + 1], + relatedEntries, + }; } /** Gövdeyi render eden Astro bileşeni. D1 tarafında karşılığı yok: orada @@ -27,7 +88,7 @@ export async function renderEntry(entry: any) { /** RSS gövdeleri. Koleksiyon girdileri gövdeyi zaten taşıyor. */ export async function getPublishedWithBody(limit: number): Promise { - return (await getPublished()).slice(0, limit); + return getPublished({ limit }); } /** Haber sayfasının gövdesi. Statik modda gövde girdinin içinde geliyor ve diff --git a/src/data/newsSource.workers.ts b/src/data/newsSource.workers.ts index 46921c8..7f1a521 100644 --- a/src/data/newsSource.workers.ts +++ b/src/data/newsSource.workers.ts @@ -2,13 +2,31 @@ * * `astro:content` BİLEREK içeri alınmıyor; gerekçesi `newsSource.ts` içinde. */ -import { getArticleFromD1, getPublishedFromD1 } from './equinoxHaberD1'; +import { + getArticleFromD1, + getArticlePageFromD1, + getPublishedCountFromD1, + getPublishedFromD1, + type PublishedD1Options, +} from './equinoxHaberD1'; import { getDatabase } from '#runtime-env'; -export async function getPublished(): Promise { +export async function getPublished(options: PublishedD1Options = {}): Promise { const db = getDatabase(); if (!db) throw new Error('D1 binding yok: sunucu modunda haber kaynağı okunamıyor.'); - return getPublishedFromD1(db); + return getPublishedFromD1(db, options); +} + +export async function getPublishedCount(): Promise { + const db = getDatabase(); + if (!db) throw new Error('D1 binding yok.'); + return getPublishedCountFromD1(db); +} + +export async function getArticlePage(slug: string) { + const db = getDatabase(); + if (!db) throw new Error('D1 binding yok.'); + return getArticlePageFromD1(db, slug); } /** Sunucu modunda gövde markdown'dan render EDİLMEZ: `body_html` yazma anında @@ -22,7 +40,7 @@ export async function renderEntry(): Promise { export async function getPublishedWithBody(limit: number): Promise { const db = getDatabase(); if (!db) throw new Error('D1 binding yok.'); - return getPublishedFromD1(db, { withBody: true, limit }); + return getPublishedFromD1(db, { withBody: true, limit, includeTags: true, includeSources: false }); } /** Gösterilen haberin gövdesi. Liste sorgusunda gövde yok; yalnız bu tek diff --git a/src/middleware.ts b/src/middleware.ts index 170d068..9ca244e 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -4,11 +4,12 @@ import { oturumCereziVarMi } from './server/session'; /* Kenar önbelleği. * - * Neden gerekli, ölçümle: bir sayfa isteği D1'den **4225 satır** okuyor — - * 587 haber üstbilgisi, 3041 etiket, 597 kaynak. Liste, ilgili haberler ve - * önceki/sonraki hesabı bütün koleksiyonu istiyor. Ücretsiz planın günlük - * sınırı 5 milyon satır okuma, yani önbelleksiz tavan ~1.180 sayfa - * görüntüleme/gün. Bir haber sitesi için bu az. + * İlk D1 sürümü her sayfa isteğinde bütün haber üstbilgilerini, etiketleri ve + * kaynakları okuyordu; tek istek binlerce satıra çıkıyordu. Haber/listeler artık + * hedefli ve sayfalı sorgular kullanıyor: tekil haberde yalnız mevcut kayıt, + * ilişkileri, komşuları ve ilgili adaylar; arşivde yalnız istenen sayfa okunuyor. + * Kenar önbelleği bu düşük temel maliyeti de tekrar trafikte sıfıra yaklaştırmak + * ve D1 geçici olarak cevap veremezse son iyi sayfayı sunabilmek için korunuyor. * * Süre 60 saniye. Bu bir tercih ve gerekçesi şu: sistemin varlık sebebi * yayının ve DÜZELTMENİN hızlı yansıması. Bir dakikalık bayatlık, statik @@ -25,8 +26,8 @@ import { oturumCereziVarMi } from './server/session'; * isteğin düştüğü veri merkezini temizlediği için gerçek bir geçersizleştirme * aracı değil; sürüm anahtarı her yerde çalışıyor. * - * Maliyet istek başına bir satır okuma. Önbellek isabetinde sayfa maliyeti - * 4225 satırdan 1 satıra iniyor. + * Önbellek anahtarını üretmek için istek başına yalnız `site_state` sürüm satırı + * okunuyor. Önbellek isabetinde sayfanın diğer D1 sorguları hiç çalışmıyor. * * Statik derlemede `caches` yok; ara katman hiçbir şey yapmadan geçiyor. */ diff --git a/src/pages/[...slug].astro b/src/pages/[...slug].astro index 71b1cb0..60bf46a 100644 --- a/src/pages/[...slug].astro +++ b/src/pages/[...slug].astro @@ -1,5 +1,5 @@ --- -import { getArticleBody, renderEntry } from '#news-source'; +import { getArticlePage, renderEntry } from '#news-source'; import NewsLayout from '../layouts/NewsLayout.astro'; import NewsCard from '../components/news/NewsCard.astro'; import { findAuthorByName } from '../data/authors'; @@ -15,10 +15,11 @@ export async function getStaticPaths() { const routeSlug = Astro.params.slug; -/* Kaynak seçimi `getPublishedEquinoxHaber` içinde; burası hangi kaynaktan - okuduğunu bilmiyor. */ -const allEntries = await getPublishedEquinoxHaber(); -const entry = allEntries.find((item) => item.id === routeSlug); +/* Sunucu modunda tekil haber yolu bütün arşivi okumaz. D1 kaynağı mevcut + haber + komşular + ilgili üçlü için hedefli sorgular kullanır; statik kaynak + aynı şekli içerik koleksiyonundan kurar. */ +const articlePage = routeSlug ? await getArticlePage(routeSlug) : null; +const entry = articlePage?.entry; if (!entry) { /* Yayından kaldırılmış bir haber mi? @@ -58,33 +59,19 @@ if (!entry) { ölçüldü (587/587) — bkz. `worker/tools/parity-render.mjs`. */ /* Gövde listeyle birlikte gelmiyor (bkz. `equinoxHaberD1.ts`): liste yalnız üstbilgi taşıyor, gövde yalnız gösterilen haber için okunuyor. */ -const article = await getArticleBody(routeSlug!); -const bodyHtml = article?.bodyHtml ?? null; +const pageData = articlePage!; +const bodyHtml = pageData.bodyHtml; const Content = bodyHtml === null ? await renderEntry(entry) : null; -const currentIndex = allEntries.findIndex((item) => item.id === entry.id); -const nextEntry = allEntries[currentIndex - 1]; -const prevEntry = allEntries[currentIndex + 1]; -const currentTags = new Set(entry.data.tags ?? []); -const relatedEntries = allEntries - .filter((item) => item.id !== entry.id) - .map((item, index) => { - const sharedTags = (item.data.tags ?? []).filter((tag) => currentTags.has(tag)).length; - const sameCategory = normalizeNewsCategory(item.data.category) === normalizeNewsCategory(entry.data.category); - return { - entry: item, - score: (sameCategory ? 6 : 0) + sharedTags * 3 - index * 0.001, - }; - }) - .sort((a, b) => b.score - a.score) - .slice(0, 3) - .map((item) => item.entry); +const nextEntry = pageData.nextEntry; +const prevEntry = pageData.prevEntry; +const relatedEntries = pageData.relatedEntries; const authorProfile = findAuthorByName(entry.data.author); const categories = getEquinoxHaberCategories(); const categoryToken = getNewsCategoryToken(entry.data.category); /* Gövde liste sorgusuyla gelmiyor; bu kontrol ayrıca çekilen gövdeye bakmalı. Boş metne bakarsa "Kaynaklar bölümü yok" sonucuna varır ve şablon yedek Kaynaklar bölümünü basar — yani her haberde çift Kaynaklar. */ -const hasInlineSourcesSection = /^##\s+Kaynaklar\b/m.test(article?.body ?? entry.body ?? ''); +const hasInlineSourcesSection = /^##\s+Kaynaklar\b/m.test(pageData.body ?? entry.body ?? ''); const title = entry.data.title; const description = entry.data.description; diff --git a/src/pages/index.astro b/src/pages/index.astro index 142d3c9..4cf1ef1 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -14,8 +14,19 @@ import { findAuthorByName } from '../data/authors'; import NewsCard from '../components/news/NewsCard.astro'; import { gorselKaynagi, KART_SIZES, MANSET_SIZES } from '../data/gorsel'; -const allEntries = await getPublishedEquinoxHaber(); const categories = getEquinoxHaberCategories(); +const [allEntries, ...categoryEntrySets] = await Promise.all([ + getPublishedEquinoxHaber({ limit: EQUINOX_HABER_PAGE_SIZE, includeTags: false, includeSources: false }), + ...categories.map((category) => getPublishedEquinoxHaber({ + limit: EQUINOX_HABER_PAGE_SIZE, + category, + includeTags: false, + includeSources: false, + })), +]); +const categoryEntries = Object.fromEntries( + categories.map((category, index) => [category, categoryEntrySets[index] ?? []]), +) as Record; const title = 'Equinox Haber'; const description = "Kaynaklı, kısa ve okunaklı Türkçe haber akışı. Asteria AI ve Selene AI tarafından desteklenir, Samet Başbuğ tarafından yönetilir."; @@ -127,8 +138,7 @@ const mansetGorseli = allPanel.leadEntry?.data.heroImage const panels: Record = { 'Tümü': toPanel(allPanel) }; for (const category of categories) { - const categoryEntries = allEntries.filter((entry) => entry.data.category === category); - panels[category] = toPanel(buildPanel(categoryEntries)); + panels[category] = toPanel(buildPanel(categoryEntries[category] ?? [])); } const categorySlugMap = Object.fromEntries(categories.map((category) => [slugifyNewsCategory(category), category])); diff --git a/src/pages/news-sitemap.xml.ts b/src/pages/news-sitemap.xml.ts index 007b451..4e5084b 100644 --- a/src/pages/news-sitemap.xml.ts +++ b/src/pages/news-sitemap.xml.ts @@ -8,9 +8,12 @@ const MAX_ITEMS = 1000; export async function GET(context: { site?: URL }) { const site = context.site?.toString() ?? 'https://haber.sametbasbug.dev'; const now = Date.now(); - const entries = (await getPublishedEquinoxHaber()) - .filter((entry) => now - entry.data.pubDate.getTime() <= NEWS_WINDOW_MS) - .slice(0, MAX_ITEMS); + const entries = await getPublishedEquinoxHaber({ + since: new Date(now - NEWS_WINDOW_MS), + limit: MAX_ITEMS, + includeTags: false, + includeSources: false, + }); const body = `\n\n${entries .map((entry) => { diff --git a/src/pages/sayfa/[page]/index.astro b/src/pages/sayfa/[page]/index.astro index 3251236..8f07a99 100644 --- a/src/pages/sayfa/[page]/index.astro +++ b/src/pages/sayfa/[page]/index.astro @@ -3,7 +3,7 @@ import type { GetStaticPaths, Page } from 'astro'; import NewsLayout from '../../../layouts/NewsLayout.astro'; import NewsStream from '../../../components/news/NewsStream.astro'; import ArchivePagination from '../../../components/news/ArchivePagination.astro'; -import { EQUINOX_HABER_ARCHIVE_PAGE_SIZE, getEquinoxHaberCategories, getPublishedEquinoxHaber, type EquinoxHaberEntry } from '../../../data/equinoxHaber'; +import { EQUINOX_HABER_ARCHIVE_PAGE_SIZE, getEquinoxHaberCategories, getPublishedEquinoxHaber, getPublishedEquinoxHaberCount, type EquinoxHaberEntry } from '../../../data/equinoxHaber'; export const getStaticPaths = (async ({ paginate }) => { const entries = await getPublishedEquinoxHaber(); @@ -27,9 +27,9 @@ interface Props { let { page } = Astro.props as Props; if (!page) { - const entries = await getPublishedEquinoxHaber(); const size = EQUINOX_HABER_ARCHIVE_PAGE_SIZE; - const lastPage = Math.max(1, Math.ceil(entries.length / size)); + const totalEntries = await getPublishedEquinoxHaberCount(); + const lastPage = Math.max(1, Math.ceil(totalEntries / size)); const requested = Number(Astro.params.page ?? 1); /* Aralık dışı sayfa numarası uydurulmuyor: statik modda o adres hiç @@ -38,8 +38,14 @@ if (!page) { return new Response(null, { status: 404 }); } + const entries = await getPublishedEquinoxHaber({ + limit: size, + offset: (requested - 1) * size, + includeTags: false, + includeSources: false, + }); page = { - data: entries.slice((requested - 1) * size, requested * size), + data: entries, currentPage: requested, lastPage, } as Props['page']; diff --git a/src/server/routes/sitemap-0.ts b/src/server/routes/sitemap-0.ts index a7871b3..779dcf1 100644 --- a/src/server/routes/sitemap-0.ts +++ b/src/server/routes/sitemap-0.ts @@ -30,7 +30,7 @@ const STATIC_PATHS = [ export const GET: APIRoute = async ({ site }) => { const base = (site ?? new URL('https://haber.sametbasbug.dev')).toString().replace(/\/$/, ''); - const entries = await getPublished(); + const entries = await getPublished({ includeTags: false, includeSources: false }); const pageCount = Math.max(1, Math.ceil(entries.length / EQUINOX_HABER_ARCHIVE_PAGE_SIZE)); const paths = [ diff --git a/worker/tools/parity-page.mjs b/worker/tools/parity-page.mjs index 157680f..104517d 100644 --- a/worker/tools/parity-page.mjs +++ b/worker/tools/parity-page.mjs @@ -34,6 +34,10 @@ const slugs = readdirSync(`${ROOT}src/content/equinoxHaber`) * - `globalThis.process??={}` öneki: Cloudflare adaptörünün satır içi * betiklere eklediği polyfill. Derleme aracının eklentisi; sayfanın * içeriğiyle, düzeniyle veya davranışıyla ilgisi yok. + * - `-webkit-backdrop-filter`: Cloudflare SSR CSS derlemesi standart + * `backdrop-filter` kuralının yanına aynı değerde vendor prefix ekliyor; + * statik derleme eklemiyor. Aynı stilin uyumluluk kopyası olduğu için + * karşılaştırmadan çıkarılıyor. * * `&` kaçışı için normalizasyon YOK ve olmamalı: site de Worker da artık * `unified` kullanıyor, yani o fark kaynağında ortadan kalktı. Buraya bir @@ -42,7 +46,8 @@ const slugs = readdirSync(`${ROOT}src/content/equinoxHaber`) const normalize = (html) => html .replaceAll(/data-astro-cid-[a-z0-9]+/gu, "data-astro-cid-X") .replaceAll(/\/_astro\/([A-Za-z0-9_.-]+?)\.[A-Za-z0-9_-]{8}\.(css|js)/gu, "/_astro/$1.X.$2") - .replaceAll("globalThis.process??={},globalThis.process.env??={};", ""); + .replaceAll("globalThis.process??={},globalThis.process.env??={};", "") + .replaceAll(/-webkit-backdrop-filter:[^;{}]+;?/gu, ""); let checked = 0, skipped = 0; const failures = [];