refactor: ortak tipler @/types/yokatlas + features/rapor/types'a çıkarıldı, server-only korumaları eklendi (Faz 1)

- Program/PUAN_TURLERI/DilimKey/RankResults/UniturGrubu/ProgramNetSatiri/
  SihirbazFacetleri artık src/types/yokatlas.ts'te; lib/db.ts yalnızca sorgu
  fonksiyonları barındırıyor
- RaporSonuc/RaporKapsam/RaporParams/MaskeliRapor src/features/rapor/types/
  rapor.ts'e taşındı; ölü kalan TercihSchema/RaporSchema silindi
- 11 lib dosyasına import "server-only" eklendi; scripts'in düz node ile
  import ettiği dosyalara (db, appdb, rapor-havuzu, tadimlik-havuzu, ai/client,
  ai/cagri, credits) bilinçli eklenmedi
- Davranış değişikliği yok; pnpm build yeşil

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-08-08 01:06:06 +03:00
parent 7518baccfa
commit 820eea7eca
62 changed files with 1318 additions and 215 deletions

View File

@@ -1,11 +1,10 @@
import { NextResponse, type NextRequest } from "next/server";
import { dilimAra, rankWindowFacets } from "@/lib/db";
import {
PUAN_TURLERI,
dilimAra,
rankWindowFacets,
type DilimKey,
type PuanTuruKey,
} from "@/lib/db";
} from "@/types/yokatlas";
import { KATEGORILER } from "@/lib/kategoriler";
// Sihirbaz modalı için: sıralamaya uygun kategori/il/üniversite tipi

View File

@@ -1,10 +1,10 @@
import { NextResponse, type NextRequest } from "next/server";
import { dilimAra } from "@/lib/db";
import {
PUAN_TURLERI,
dilimAra,
type DilimKey,
type PuanTuruKey,
} from "@/lib/db";
} from "@/types/yokatlas";
const SAYFA_BOYU = 20;

View File

@@ -10,10 +10,11 @@ import {
import { anahtarVar, SORUMLULUK_REDDI } from "@/lib/ai/client";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { sohbetAkisi } from "@/lib/ai/cagri";
import { listeOzetiCikar, type RaporSonuc } from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
import { listeOzetiCikar } from "@/lib/ai/rapor";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import type { RaporParams } from "@/features/rapor/types/rapor";
import { secimOzeti } from "@/lib/sihirbaz";
import { PUAN_TURLERI } from "@/lib/db";
import { PUAN_TURLERI } from "@/types/yokatlas";
import { ACIK_SATIR } from "@/lib/rapor-maske";
const MAX_GECMIS = 12;

View File

@@ -3,7 +3,7 @@
// batch üretilmiştir — bu uç LLM'e istek atmaz, salt SELECT'tir.
import { NextResponse } from "next/server";
import { PUAN_TURLERI, type PuanTuruKey } from "@/lib/db";
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
import { KATEGORILER } from "@/lib/kategoriler";
import { kategoriSlugBul, tadimlikSec } from "@/lib/tadimlik-havuzu";

View File

@@ -20,7 +20,7 @@ import { RaporListesi } from "@/components/rapor-listesi";
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
import { TercihHaritasi } from "@/components/tercih-haritasi";
import { pinleriTuret } from "@/lib/harita-pinler";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import { SohbetClient, type DanismanOzeti, type Mesaj } from "./sohbet-client";
import { RevizyonKutusu } from "./revizyon-kutusu";

View File

@@ -6,11 +6,12 @@ import { verifySession, getCurrentUser } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { URUNLER, MAX_REVIZYON } from "@/lib/credits";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import type { RaporParams } from "@/features/rapor/types/rapor";
import { ViewportPortal } from "@/components/viewport-portal";
import { SiteFooter } from "@/components/site-footer";
import { raporMaskele, ACIK_SATIR, type MaskeliRapor } from "@/lib/rapor-maske";
import { raporMaskele, ACIK_SATIR } from "@/lib/rapor-maske";
import type { MaskeliRapor } from "@/features/rapor/types/rapor";
import type { Mesaj } from "./sohbet-client";
import { ListemGovde } from "./listem-govde";
import { ListemBosCta } from "./listem-bos-cta";

View File

@@ -18,7 +18,7 @@ import Link from "next/link";
import { ArrowRight } from "lucide-react";
import { useTercihProfili } from "@/components/manuel-liste/profil-kapisi-store";
import { PUAN_TURU_ETIKET, sonucHref } from "@/lib/sihirbaz";
import type { DilimKey, SihirbazFacetleri } from "@/lib/db";
import type { DilimKey, SihirbazFacetleri } from "@/types/yokatlas";
export type DemoVeri = {
facetler: SihirbazFacetleri;

View File

@@ -2,7 +2,8 @@ import type { Metadata } from "next";
import { Parallax } from "@/components/parallax";
import { PagePixelDivider, SectionEyebrow } from "@/components/pixel-decor";
import { SiteFooter } from "@/components/site-footer";
import { dilimAra, rankWindowFacets, type DilimKey } from "@/lib/db";
import { dilimAra, rankWindowFacets } from "@/lib/db";
import type { DilimKey } from "@/types/yokatlas";
import {
DemoKapanisCta,
DemoKumanda,

View File

@@ -3,9 +3,9 @@ import Image from "next/image";
import { redirect } from "next/navigation";
import { verifySession, getCurrentUser } from "@/lib/session";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { PUAN_TURLERI } from "@/lib/db";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
import { PUAN_TURLERI } from "@/types/yokatlas";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import type { RaporParams } from "@/features/rapor/types/rapor";
import { YazdirButonu } from "./yazdir-butonu";
export const metadata: Metadata = {

View File

@@ -4,7 +4,7 @@ import { and, eq, sql } from "drizzle-orm";
import { getSession, getCurrentUser } from "@/lib/session";
import { appDb, schema } from "@/lib/appdb";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { PUAN_TURLERI, type PuanTuruKey } from "@/lib/db";
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
import {
spendCredits,
grantCredits,
@@ -16,9 +16,11 @@ import {
raporUret,
listeOzetiCikar,
RaporUretimHatasi,
type RaporSonuc,
} from "@/lib/ai/rapor";
import type { RaporParams } from "@/lib/rapor-havuzu";
import type {
RaporParams,
RaporSonuc,
} from "@/features/rapor/types/rapor";
import { sihirbazDogrula, type SihirbazSecimleri } from "@/lib/sihirbaz";
import { raporMaskele } from "@/lib/rapor-maske";

View File

@@ -1,10 +1,6 @@
import type { Metadata } from "next";
import {
PUAN_TURLERI,
rankWindowFacets,
searchByRank,
type PuanTuruKey,
} from "@/lib/db";
import { rankWindowFacets, searchByRank } from "@/lib/db";
import { PUAN_TURLERI, type PuanTuruKey } from "@/types/yokatlas";
import { getSession } from "@/lib/session";
import { kullanicininRaporu } from "@/lib/rapor-kaydi";
import { tadimlikSec } from "@/lib/tadimlik-havuzu";

View File

@@ -10,7 +10,7 @@ import { Button } from "@/components/ui/button";
import { PixelField } from "@/components/pixel-decor";
import { useGorunumOlayi } from "@/components/use-gorunum-olayi";
import { olay } from "@/lib/analitik";
import type { SihirbazFacetleri } from "@/lib/db";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import {
SIHIRBAZ_AC_EVENT,
SIHIRBAZ_STORAGE_KEY,

View File

@@ -7,7 +7,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog";
import { SihirbazAdimlarLazy } from "@/components/sihirbaz-adimlar-lazy";
import type { SihirbazFacetleri } from "@/lib/db";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import type { SihirbazSecimleri } from "@/lib/sihirbaz";
/**

View File

@@ -13,7 +13,7 @@ import { UniversiteKonumHaritasi } from "@/components/universite-konum-haritasi"
import { UniversiteProgramTablosu } from "@/components/universite-program-tablosu";
import { UniLogo } from "@/components/uni-logo";
import { uniLogoYolu } from "@/lib/uni-logolar";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
const sayi = (n: number) => n.toLocaleString("tr-TR");

View File

@@ -3,7 +3,7 @@
import { useMemo, useRef, useState } from "react";
import Link from "next/link";
import { ChevronDown, ChevronUp } from "lucide-react";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
import { trBaslikDuzeni } from "@/lib/slug";
import { Sayfalama, SAYFA_BOYU } from "@/components/sayfalama";
import {

View File

@@ -28,7 +28,7 @@ import {
import { authClient } from "@/lib/auth-client";
import { olay, siraKovasi } from "@/lib/analitik";
import { useTercihProfili } from "@/components/manuel-liste/profil-kapisi-store";
import type { SihirbazFacetleri } from "@/lib/db";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import {
sonucHref,
tercihProfiliYaz,

View File

@@ -22,7 +22,7 @@ import {
profilDuzenlemeyiAc,
useTercihProfili,
} from "@/components/manuel-liste/profil-kapisi-store";
import type { SihirbazFacetleri } from "@/lib/db";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import {
PUAN_TURU_ETIKET,
sonucHref,

View File

@@ -4,7 +4,7 @@
// Layout'ta bir kez monte edilen TercihProfiliKapisi bu store'a abone olur.
import { useSyncExternalStore } from "react";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
import {
PROFIL_DEGISTI_EVENT,
sonucHref,

View File

@@ -2,7 +2,7 @@
import { Check, Plus } from "lucide-react";
import { toast } from "sonner";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
import { olay } from "@/lib/analitik";
import {
listedeMi,

View File

@@ -6,7 +6,7 @@
// (girişsiz kullanıcı da liste kurabilsin diye sunucuya yazmıyoruz).
import { useSyncExternalStore } from "react";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
import { riskHesapla } from "@/lib/risk";
import {
tercihProfiliOku,

View File

@@ -26,7 +26,7 @@ import {
SihirbazAdimlarLazy,
preloadSihirbazAdimlar,
} from "@/components/sihirbaz-adimlar-lazy";
import type { SihirbazFacetleri } from "@/lib/db";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import { olay, siraKovasi } from "@/lib/analitik";
import {
tercihProfiliYaz,

View File

@@ -1,5 +1,5 @@
import { TrendingDown, TrendingUp } from "lucide-react";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
/** Taban sıra sütunlarının yapısal alt kümesi: hem DB programı hem de
* rapordaki kaydedilmiş sıra geçmişi (bkz. raporSiraSerisi) bu tipe uyar. */

View File

@@ -8,7 +8,7 @@
import { useState } from "react";
import { ListChecks } from "lucide-react";
import type { ProgramNetSatiri } from "@/lib/db";
import type { ProgramNetSatiri } from "@/types/yokatlas";
import { olay } from "@/lib/analitik";
import {
Dialog,

View File

@@ -13,7 +13,7 @@ import {
Scale,
ShieldCheck,
} from "lucide-react";
import type { DilimKey, Program, PuanTuruKey, RankResults } from "@/lib/db";
import type { DilimKey, Program, PuanTuruKey, RankResults } from "@/types/yokatlas";
import { riskHesapla, type RiskSeviyesi } from "@/lib/risk";
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug";
import {

View File

@@ -27,7 +27,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { bolumSayfaSlug, uniSayfaSlug } from "@/lib/slug";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import {
RISK_ETIKET,
dilimdenRisk,

View File

@@ -19,7 +19,7 @@ import { IlSecimHaritasi } from "@/components/il-secim-haritasi";
import { normalizeIlAdi } from "@/lib/harita";
import { olay } from "@/lib/analitik";
import { trBaslikDuzeni } from "@/lib/slug";
import type { SihirbazFacetleri } from "@/lib/db";
import type { SihirbazFacetleri } from "@/types/yokatlas";
import {
ONCELIKLER,
UNIVERSITE_TIPI_ETIKET,

View File

@@ -1,7 +1,7 @@
"use client";
import Link from "next/link";
import type { Program } from "@/lib/db";
import type { Program } from "@/types/yokatlas";
import {
ProgramSiraGecmisi,
ProgramTabanTrendi,

View File

@@ -0,0 +1,69 @@
// Rapor domain'inin ortak tipleri — client bileşenleri ve server katmanı
// birlikte tüketir. Üretim mantığı @/lib/ai/rapor.ts'te (server-only) yaşar
// ve bu tiplere karşı derlenir; alan ekleme/çıkarma burada yapılır.
import type { DilimKey, PuanTuruKey, UniturGrubu } from "@/types/yokatlas";
export type RaporTercih = {
programId: string;
sira: number;
dilim: DilimKey;
gerekce: string;
riskNotu: string;
trendOzeti: string;
};
/**
* Listenin havuza uyarlanma bilgisi. uyarlandi=true ise dağılım ideal
* 5/13/6'dan sapmıştır (havuz yetersizdi) ve UI dürüst bir not gösterir.
* Eski kayıtlı raporlarda yok → opsiyonel.
*/
export type RaporKapsam = {
toplam: number;
dagilim: Record<DilimKey, number>;
uyarlandi: boolean;
};
// uyarilar şemada zorunlu ama eski kayıtlı raporlarda yok → tipte opsiyonel
export type RaporSonuc = {
tercihler: RaporTercih[];
genelDegerlendirme: string;
uyarilar?: string[];
kapsam?: RaporKapsam;
// Dev mock üretimi işareti: gerçek AI aktifken bayat mock kayıtları
// ayıklamak için (bkz. src/lib/rapor-kaydi.ts)
devMock?: boolean;
// Render için havuzdan zenginleştirilmiş program bilgisi
// efektifSira: COALESCE(sira2025, sira2024) — eski kayıtlı raporlarda yok
// siraGecmisi: [2021..2025] taban sıralamaları — deterministik trend oku
programlar: Record<
string,
{
isim: string;
universite: string;
il: string | null;
unitur: string | null;
efektifSira?: number | null;
siraGecmisi?: (number | null)[];
}
>;
};
/**
* Maske çıktısı: kilitli uyarıların yalnızca SAYISI iner, metinleri inmez.
* RaporSchema'ya (LLM çıktısı) eklenmez — bu alan yalnızca maskede doğar.
*/
export type MaskeliRapor = RaporSonuc & { kilitliUyariSayisi?: number };
export interface RaporParams {
sira: number;
tur: PuanTuruKey;
// Sihirbaz seçimleri (yeni akış)
kategoriler?: string[];
iller?: string[];
universiteTipi?: UniturGrubu | "farketmez";
oncelikler?: string[];
// Eski akış alanları (geriye uyum — eski kayıtlı raporlar)
il?: string;
notlar?: string;
}

View File

@@ -1,4 +1,10 @@
import "server-only";
import { z } from "zod";
import type {
RaporKapsam,
RaporParams,
RaporSonuc,
} from "@/features/rapor/types/rapor";
import { anahtarVar } from "./client";
import { yapilandirilmisUret } from "./cagri";
import {
@@ -9,27 +15,11 @@ import {
IDEAL_HEDEF,
type DilimHedef,
type Dilim,
type RaporParams,
type AdayProgram,
} from "../rapor-havuzu";
import { secimOzeti } from "../sihirbaz";
import { RISK_ETIKET, dilimdenRisk, riskHesapla } from "../risk";
const TercihSchema = z.object({
programId: z.string(),
sira: z.number().int(),
dilim: z.enum(["hayal", "dengeli", "garanti"]),
gerekce: z.string(),
riskNotu: z.string(),
trendOzeti: z.string(),
});
const RaporSchema = z.object({
tercihler: z.array(TercihSchema),
genelDegerlendirme: z.string(),
uyarilar: z.array(z.string()).min(2).max(3),
});
// Yapay zekâdan istenen küçük şema (kullanıcı kararı, 6 Ağu 2026): riskNotu
// ve trendOzeti gerçek sıra geçmişinden deterministik üretilir; dilim iskeleti
// ve tercih sırası da koddan gelir. Dilim başına SABİT uzunluklu ayrı listeler
@@ -63,40 +53,6 @@ const AiRaporGevsek = z.object({
uyarilar: z.array(z.string()),
});
/**
* Listenin havuza uyarlanma bilgisi. uyarlandi=true ise dağılım ideal
* 5/13/6'dan sapmıştır (havuz yetersizdi) ve UI dürüst bir not gösterir.
* Eski kayıtlı raporlarda yok → opsiyonel.
*/
export type RaporKapsam = {
toplam: number;
dagilim: Record<Dilim, number>;
uyarlandi: boolean;
};
// uyarilar şemada zorunlu ama eski kayıtlı raporlarda yok → tipte opsiyonel
export type RaporSonuc = Omit<z.infer<typeof RaporSchema>, "uyarilar"> & {
uyarilar?: string[];
kapsam?: RaporKapsam;
// Dev mock üretimi işareti: gerçek AI aktifken bayat mock kayıtları
// ayıklamak için (bkz. src/lib/rapor-kaydi.ts)
devMock?: boolean;
// Render için havuzdan zenginleştirilmiş program bilgisi
// efektifSira: COALESCE(sira2025, sira2024) — eski kayıtlı raporlarda yok
// siraGecmisi: [2021..2025] taban sıralamaları — deterministik trend oku
programlar: Record<
string,
{
isim: string;
universite: string;
il: string | null;
unitur: string | null;
efektifSira?: number | null;
siraGecmisi?: (number | null)[];
}
>;
};
function sistemMetni(hedef: DilimHedef): string {
const adet = (d: Dilim) =>
hedef[d] === 0 ? "BOŞ liste [] döndür" : `TAM ${hedef[d]} program`;

View File

@@ -1,3 +1,4 @@
import "server-only";
/**
* Rybbit'e sunucudan olay gönderimi (POST /api/track). Yalnızca sunucu
* tarafından çağrılır (lib/odeme.ts) — anahtar NEXT_PUBLIC_ değil.
@@ -50,4 +51,4 @@ export async function sunucuOlayi(
} catch {
// Yut: analitik hatası ödeme sonucunu etkilemez
}
}
}

View File

@@ -1,3 +1,4 @@
import "server-only";
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { magicLink } from "better-auth/plugins";
@@ -97,4 +98,4 @@ export const auth = betterAuth({
},
});
export type Session = typeof auth.$Infer.Session;
export type Session = typeof auth.$Infer.Session;

View File

@@ -1,45 +1,18 @@
// server-only guard'ı bilinçli yok: scripts/tadimlik-uret.ts bu modülü düz
// node (tsx) altında import ediyor; server-only paketi orada fırlar.
import path from "node:path";
import Database from "better-sqlite3";
import { KATEGORILER, kategoriBul } from "./kategoriler";
export type Program = {
id: string;
isim: string;
universite: string;
unitur: string | null;
il: string | null;
fakulte: string | null;
tur: string;
sure: number | null;
sira2025: number | null;
sira2024: number | null;
sira2023: number | null;
sira2022: number | null;
sira2021: number | null;
puan2025: number | null;
kontenjan2025: number | null;
yerlesen2025: number | null;
};
export const PUAN_TURLERI = {
say: "SAYISAL",
ea: "EŞİT AĞIRLIK",
soz: "SÖZEL",
dil: "DİL",
tyt: "TYT",
} as const;
export type PuanTuruKey = keyof typeof PUAN_TURLERI;
export type DilimKey = "hayal" | "dengeli" | "garanti";
export type RankResults = {
hayal: Program[];
dengeli: Program[];
garanti: Program[];
/** Dilim başına pencereye düşen TOPLAM program sayısı (limit'ten bağımsız) */
toplam: Record<DilimKey, number>;
};
import {
PUAN_TURLERI,
type DilimKey,
type Program,
type ProgramNetSatiri,
type PuanTuruKey,
type RankResults,
type SihirbazFacetleri,
type UniturGrubu,
} from "@/types/yokatlas";
// Dev'de hot-reload başına yeni bağlantıılmasın diye global cache
const globalForDb = globalThis as unknown as { yokatlasDb?: Database.Database };
@@ -67,10 +40,6 @@ export const EFEKTIF_SIRA = "COALESCE(sira2025, sira2024)";
* sıralamasından küçükse geçen yıl oraya yerleşmek daha iyi dereceyle mümkündü
* demektir (hayal), büyükse daha güvenli demektir (garanti).
*/
export type UniturGrubu = "devlet" | "vakif";
// Vakıf sayılan unitur değerleri (bkz. DISTINCT unitur: VAKIF, VAKIF MYO,
// YURTDISI VAKIF). Devlet dışı her şey "vakıf" grubuna girer.
function uniturFiltreSql(grup?: UniturGrubu): { sql: string; args: string[] } {
if (grup === "devlet") return { sql: "AND unitur = ?", args: ["DEVLET"] };
if (grup === "vakif")
@@ -189,30 +158,6 @@ export function searchByRank(
};
}
/** netler tablosunun bir yılı: yerleşen son kişinin ders bazlı netleri. */
export type ProgramNetSatiri = {
yil: number;
puanTuru: string | null;
tabanPuan: number | null;
obp: number | null;
tytTrkNet: number | null;
tytSosNet: number | null;
tytMatNet: number | null;
tytFenNet: number | null;
aytMatNet: number | null;
aytFizNet: number | null;
aytKimNet: number | null;
aytBioNet: number | null;
aytTdeNet: number | null;
aytTrh1Net: number | null;
aytCog1Net: number | null;
aytTrh2Net: number | null;
aytCog2Net: number | null;
aytDinNet: number | null;
aytFelNet: number | null;
ydtYdilNet: number | null;
};
/**
* Programa yerleşen son kişinin yıl bazlı netleri, yeniden eskiye sıralı.
* Veri scripts/detay.ts ile YÖK Atlas /api/netler/search'ten doldurulur.
@@ -230,14 +175,6 @@ export function netlerGetir(programId: string): ProgramNetSatiri[] {
.all(programId) as ProgramNetSatiri[];
}
export type SihirbazFacetleri = {
kategoriler: { ad: string; adet: number }[];
iller: { il: string; adet: number }[];
uniturler: { grup: UniturGrubu; adet: number }[];
/** Penceredeki (filtre uygulanmamış) toplam program sayısı */
toplam: number;
};
// Sihirbaz bubble'ları için erişilebilir program penceresi: hayal için
// gerçekçi alt sınırdan (sira*0.5) itibaren AÇIK uçlu — garanti tarafı artık
// sınırsız olduğundan üst kesim yok. Böylece 500B sıradaki aday Tıp bubble'ı

View File

@@ -1,3 +1,4 @@
import "server-only";
import type { EpostaIcerik } from "./eposta";
/**
@@ -33,4 +34,4 @@ export async function epostaGonder(
console.error(`[eposta] Gönderilemedi (${to}):`, error);
throw new Error(`Resend: ${error.name}${error.message}`);
}
}
}

View File

@@ -1,3 +1,4 @@
import "server-only";
/**
* E-posta şablonları.
*
@@ -202,4 +203,4 @@ export function krediHatirlatmaEpostasi(): EpostaIcerik {
"yükleyebilirsin — listen ve sohbet geçmişin olduğu gibi durur.",
].join("\n"),
};
}
}

View File

@@ -1,6 +1,6 @@
// Rapordan Türkiye haritası pinleri türetir — /sonuc ve /listem ortak kullanır.
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
import type { HaritaPin } from "@/components/tercih-haritasi";
import { ilKonum, uniAdiNormalize, uniKonum } from "@/lib/harita";
import { dilimdenRisk, riskHesapla, type RiskSeviyesi } from "@/lib/risk";

View File

@@ -1,3 +1,4 @@
import "server-only";
import Iyzipay from "iyzipay";
// iyzipay CJS + callback tabanlı; burada promisify'lı ince bir katman var.
@@ -109,4 +110,4 @@ export function retrieveCheckoutForm(
},
);
});
}
}

View File

@@ -1,9 +1,6 @@
import {
getDb,
SELECT_COLS,
EFEKTIF_SIRA,
type Program,
} from "./db";
import "server-only";
import { getDb, SELECT_COLS, EFEKTIF_SIRA } from "./db";
import type { Program } from "@/types/yokatlas";
import { kategoriBul, type Kategori } from "./kategoriler";
import { uniAdiNormalize } from "./harita";
import { bolumBazAdi, turkishSlugify, trBaslikDuzeni } from "./slug";

View File

@@ -1,3 +1,4 @@
import "server-only";
import { and, eq, isNull, lt, lte } from "drizzle-orm";
import { appDb, schema } from "./appdb";
import { RAPOR_KREDI } from "./credits";
@@ -84,4 +85,4 @@ export function krediHatirlatmaBaslat(): void {
// gecikmeyle açılış taraması da yapılır.
setTimeout(tik, 60_000);
setInterval(tik, ARALIK_MS);
}
}

View File

@@ -1,3 +1,4 @@
import "server-only";
import { and, eq } from "drizzle-orm";
import { appDb, schema } from "./appdb";
import { grantCredits, URUNLER } from "./credits";
@@ -73,4 +74,4 @@ export async function odemeyiSonuclandir(
return "paid";
}
export { URUNLER };
export { URUNLER };

View File

@@ -1,10 +1,12 @@
// server-only guard'ı bilinçli yok: scripts/tadimlik-uret.ts bu modülü düz
// node (tsx) altında import ediyor; server-only paketi orada fırlar.
import { searchByRank } from "./db";
import {
searchByRank,
PUAN_TURLERI,
type Program,
type PuanTuruKey,
type UniturGrubu,
PUAN_TURLERI,
} from "./db";
} from "@/types/yokatlas";
import type { RaporParams } from "@/features/rapor/types/rapor";
import { kategoriEslesir } from "./kategoriler";
export type Dilim = "hayal" | "dengeli" | "garanti";
@@ -13,19 +15,6 @@ export interface AdayProgram extends Program {
dilim: Dilim;
}
export interface RaporParams {
sira: number;
tur: PuanTuruKey;
// Sihirbaz seçimleri (yeni akış)
kategoriler?: string[];
iller?: string[];
universiteTipi?: UniturGrubu | "farketmez";
oncelikler?: string[];
// Eski akış alanları (geriye uyum — eski kayıtlı raporlar)
il?: string;
notlar?: string;
}
export interface HavuzSonuc {
havuz: AdayProgram[];
// Havuz 24'e ulaşmak için hangi filtreler gevşetildi

View File

@@ -1,7 +1,8 @@
import "server-only";
import { eq } from "drizzle-orm";
import { appDb, schema } from "@/lib/appdb";
import { anahtarVar } from "@/lib/ai/client";
import type { RaporSonuc } from "@/lib/ai/rapor";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
// Kullanıcının kayıtlı raporunu okuyan TEK kapı. Rapor tablosunu okuyan her
// yer (sayfalar, action'lar, soru API'si) buradan geçmeli; doğrudan
@@ -31,4 +32,4 @@ export async function kullanicininRaporu(userId: string) {
});
if (satir?.result && bayatMockMu(satir.result as RaporSonuc)) return null;
return satir ?? null;
}
}

View File

@@ -3,16 +3,14 @@
// Yalnızca Yapay Zeka'nın ürettiği kişisel gerekçe/risk/trend yorumu maskelenir;
// böylece istemciye ücretli analiz metni hiç inmez.
import type { RaporSonuc } from "./ai/rapor";
import "server-only";
import type {
MaskeliRapor,
RaporSonuc,
} from "@/features/rapor/types/rapor";
export const ACIK_SATIR = 3;
/**
* Maske çıktısı: kilitli uyarıların yalnızca SAYISI iner, metinleri inmez.
* RaporSchema'ya (LLM çıktısı) eklenmez — bu alan yalnızca maskede doğar.
*/
export type MaskeliRapor = RaporSonuc & { kilitliUyariSayisi?: number };
const SAHTE_GEREKCE =
"Bu tercih için kişisel gerekçe paketle açılır.";
const SAHTE_RISK =

View File

@@ -1,3 +1,4 @@
import "server-only";
import fs from "node:fs";
import path from "node:path";
import { marked } from "marked";
@@ -148,4 +149,4 @@ export function getAllRehberler(): RehberOzet[] {
tarih,
}))
.sort((a, b) => b.tarih.localeCompare(a.tarih));
}
}

View File

@@ -1,3 +1,4 @@
import "server-only";
import { cache } from "react";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
@@ -32,4 +33,4 @@ export const getCurrentUser = cache(async () => {
.where(eq(schema.user.id, session.user.id))
.limit(1);
return rows[0] ?? null;
});
});

View File

@@ -3,7 +3,7 @@
// öncelikler statik kalır (veriye bağlı değil, tercih niyeti).
import { KATEGORILER } from "./kategoriler";
import type { UniturGrubu } from "./db";
import type { UniturGrubu } from "@/types/yokatlas";
export const ONCELIKLER = [
"İş garantisi",

View File

@@ -1,3 +1,4 @@
import "server-only";
import { count } from "drizzle-orm";
import { connection } from "next/server";
import { appDb, schema } from "@/lib/appdb";
@@ -107,4 +108,4 @@ export async function sosyalKanitVerisi(): Promise<SosyalKanitVerisi> {
anlikZiyaretci(),
]);
return { liste, canli };
}
}

View File

@@ -5,9 +5,9 @@
import { and, eq } from "drizzle-orm";
import { appDb, schema } from "./appdb";
import { turkishSlugify } from "./slug";
import type { PuanTuruKey } from "./db";
import type { PuanTuruKey } from "@/types/yokatlas";
import type { Dilim } from "./rapor-havuzu";
import type { RaporSonuc } from "./ai/rapor";
import type { RaporSonuc } from "@/features/rapor/types/rapor";
/** Sihirbaz kategorisi seçilmemiş kullanıcı için kova×tür başına fallback satır. */
export const GENEL_KATEGORI = "genel";

78
src/types/yokatlas.ts Normal file
View File

@@ -0,0 +1,78 @@
// YÖK Atlas veri tipleri ve puan türü sabiti — client ve server ortak tüketir.
// Sorgu fonksiyonları @/lib/db'de (server-only) yaşar; buraya runtime bağımlılığı
// olan hiçbir şey eklenmez.
export type Program = {
id: string;
isim: string;
universite: string;
unitur: string | null;
il: string | null;
fakulte: string | null;
tur: string;
sure: number | null;
sira2025: number | null;
sira2024: number | null;
sira2023: number | null;
sira2022: number | null;
sira2021: number | null;
puan2025: number | null;
kontenjan2025: number | null;
yerlesen2025: number | null;
};
export const PUAN_TURLERI = {
say: "SAYISAL",
ea: "EŞİT AĞIRLIK",
soz: "SÖZEL",
dil: "DİL",
tyt: "TYT",
} as const;
export type PuanTuruKey = keyof typeof PUAN_TURLERI;
export type DilimKey = "hayal" | "dengeli" | "garanti";
export type RankResults = {
hayal: Program[];
dengeli: Program[];
garanti: Program[];
/** Dilim başına pencereye düşen TOPLAM program sayısı (limit'ten bağımsız) */
toplam: Record<DilimKey, number>;
};
// Vakıf sayılan unitur değerleri (bkz. DISTINCT unitur: VAKIF, VAKIF MYO,
// YURTDISI VAKIF). Devlet dışı her şey "vakıf" grubuna girer.
export type UniturGrubu = "devlet" | "vakif";
/** netler tablosunun bir yılı: yerleşen son kişinin ders bazlı netleri. */
export type ProgramNetSatiri = {
yil: number;
puanTuru: string | null;
tabanPuan: number | null;
obp: number | null;
tytTrkNet: number | null;
tytSosNet: number | null;
tytMatNet: number | null;
tytFenNet: number | null;
aytMatNet: number | null;
aytFizNet: number | null;
aytKimNet: number | null;
aytBioNet: number | null;
aytTdeNet: number | null;
aytTrh1Net: number | null;
aytCog1Net: number | null;
aytTrh2Net: number | null;
aytCog2Net: number | null;
aytDinNet: number | null;
aytFelNet: number | null;
ydtYdilNet: number | null;
};
export type SihirbazFacetleri = {
kategoriler: { ad: string; adet: number }[];
iller: { il: string; adet: number }[];
uniturler: { grup: UniturGrubu; adet: number }[];
/** Penceredeki (filtre uygulanmamış) toplam program sayısı */
toplam: number;
};