Update routing in next.config.ts to redirect "/tercih-robotu" to the hero form, add simple-icons dependency in package.json, and enhance database indexing for improved query performance. Remove unused SVG files and update metadata across various pages for better SEO and clarity.
All checks were successful
Deploy / deploy (push) Successful in 14m14s

This commit is contained in:
bilalgursen
2026-08-01 17:28:38 +03:00
parent 569b9e5da2
commit 8cf0c3267d
76 changed files with 5050 additions and 255 deletions

View File

@@ -1,9 +1,17 @@
import { NextResponse, type NextRequest } from "next/server";
import { PUAN_TURLERI, rankWindowFacets, type PuanTuruKey } from "@/lib/db";
import {
PUAN_TURLERI,
dilimAra,
rankWindowFacets,
type DilimKey,
type PuanTuruKey,
} from "@/lib/db";
import { KATEGORILER } from "@/lib/kategoriler";
// Ana sayfadaki sihirbaz modalı için: sıralamaya uygun kategori/il/üniversite
// tipi facet'leri. /sonuc bu veriyi server component'te doğrudan alır; burada
// client'tan sıra girildikten sonra çekilir.
// Sihirbaz modalı için: sıralamaya uygun kategori/il/üniversite tipi
// facet'leri. Kademeli filtre: `kategori` (tekrarlı) il adımını,
// `kategori` + `il` (tekrarlı) üniversite tipi adımını süzer — bir önceki
// adımın seçimi bir sonrakinin seçeneklerini daraltır (bkz. rankWindowFacets).
export function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const sira = Number.parseInt(searchParams.get("sira") ?? "", 10);
@@ -18,5 +26,31 @@ export function GET(request: NextRequest) {
);
}
return NextResponse.json({ facetler: rankWindowFacets(sira, tur) });
const kategoriler = [
...new Set(
searchParams
.getAll("kategori")
.filter((k) => (KATEGORILER as readonly string[]).includes(k)),
),
];
const iller = [
...new Set(searchParams.getAll("il").filter((i) => i.trim().length > 0)),
].slice(0, 5);
// ?dilimler=1: /meraklisina'daki canlı dilim şeridi için dilim başına
// toplam sayılar da eklenir (limit 0 → yalnızca COUNT çalışır)
const dilimToplam =
searchParams.get("dilimler") === "1"
? Object.fromEntries(
(["hayal", "dengeli", "garanti"] as DilimKey[]).map((d) => [
d,
dilimAra(sira, tur, d, { limit: 0 }).toplam,
]),
)
: undefined;
return NextResponse.json({
facetler: rankWindowFacets(sira, tur, { kategoriler, iller }),
...(dilimToplam ? { dilimToplam } : {}),
});
}

View File

@@ -0,0 +1,86 @@
import { NextResponse, type NextRequest } from "next/server";
import { getAllBolumler, getAllUniversiteler } from "@/lib/katalog";
import { trBaslikDuzeni } from "@/lib/slug";
const SONUC_SINIRI = 6;
function normalize(metin: string): string {
return metin
.toLocaleLowerCase("tr-TR")
.replace(/ı/g, "i")
.replace(/ş/g, "s")
.replace(/ğ/g, "g")
.replace(/ü/g, "u")
.replace(/ö/g, "o")
.replace(/ç/g, "c")
.replace(/â/g, "a")
.replace(/î/g, "i")
.replace(/û/g, "u")
.normalize("NFKD")
.replace(/[̀-ͯ]/g, "");
}
function eslesmePuani(ad: string, sorgu: string): number {
const normalAd = normalize(ad);
if (normalAd === sorgu) return 0;
if (normalAd.startsWith(sorgu)) return 1;
if (normalAd.split(/\s+/).some((kelime) => kelime.startsWith(sorgu))) return 2;
return 3;
}
export function GET(request: NextRequest) {
const hamSorgu = request.nextUrl.searchParams.get("q")?.trim() ?? "";
const sorgu = normalize(hamSorgu).slice(0, 80);
if (sorgu.length < 2) {
return NextResponse.json({ bolumler: [], universiteler: [] });
}
const bolumler = getAllBolumler()
.filter((bolum) => normalize(bolum.ad).includes(sorgu))
.sort(
(a, b) =>
eslesmePuani(a.ad, sorgu) - eslesmePuani(b.ad, sorgu) ||
b.programSayisi - a.programSayisi,
)
.slice(0, SONUC_SINIRI)
.map((bolum) => ({
href: `/bolum/${bolum.slug}`,
ad: bolum.ad,
programSayisi: bolum.programSayisi,
enIyiSira: bolum.enIyiSira,
seviye:
bolum.lisans && bolum.onlisans
? "Lisans ve önlisans"
: bolum.lisans
? "Lisans"
: "Önlisans",
}));
const universiteler = getAllUniversiteler()
.filter((universite) => normalize(universite.ad).includes(sorgu))
.sort(
(a, b) =>
eslesmePuani(a.ad, sorgu) - eslesmePuani(b.ad, sorgu) ||
b.programSayisi - a.programSayisi,
)
.slice(0, SONUC_SINIRI)
.map((universite) => ({
href: `/universite/${universite.slug}`,
ad: universite.ad,
il: universite.il ? trBaslikDuzeni(universite.il) : null,
tur:
universite.unitur === "DEVLET"
? "Devlet"
: universite.unitur &&
["VAKIF", "VAKIF MYO"].includes(universite.unitur)
? "Vakıf"
: universite.unitur
? "KKTC / Yurt dışı"
: null,
programSayisi: universite.programSayisi,
fakulteSayisi: universite.fakulteSayisi,
}));
return NextResponse.json({ bolumler, universiteler });
}

View File

@@ -0,0 +1,43 @@
import { NextResponse, type NextRequest } from "next/server";
import {
PUAN_TURLERI,
dilimAra,
type DilimKey,
type PuanTuruKey,
} from "@/lib/db";
const SAYFA_BOYU = 20;
const DILIMLER: DilimKey[] = ["hayal", "dengeli", "garanti"];
// Sonuç sayfasındaki ham tablonun "daha fazla göster" sayfalaması: dilim
// başına SAYFA_BOYU program, yakından uzağa sıralı. İlk sayfa server
// component'te render edilir (bkz. sonuc/page.tsx); devamı buradan akar.
export function GET(request: NextRequest) {
const { searchParams } = request.nextUrl;
const sira = Number.parseInt(searchParams.get("sira") ?? "", 10);
const turParam = searchParams.get("tur") ?? "say";
const tur: PuanTuruKey =
turParam in PUAN_TURLERI ? (turParam as PuanTuruKey) : "say";
const dilimParam = searchParams.get("dilim") ?? "";
const offset = Number.parseInt(searchParams.get("offset") ?? "0", 10);
if (!Number.isFinite(sira) || sira < 1 || sira > 4_000_000) {
return NextResponse.json(
{ error: "Geçerli bir sıralama gerekli." },
{ status: 400 },
);
}
if (!DILIMLER.includes(dilimParam as DilimKey)) {
return NextResponse.json({ error: "Geçersiz dilim." }, { status: 400 });
}
if (!Number.isFinite(offset) || offset < 0 || offset > 100_000) {
return NextResponse.json({ error: "Geçersiz offset." }, { status: 400 });
}
const { programlar, toplam } = dilimAra(sira, tur, dilimParam as DilimKey, {
limit: SAYFA_BOYU,
offset,
});
return NextResponse.json({ programlar, toplam });
}