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:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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";
|
||||
|
||||
/**
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
69
src/features/rapor/types/rapor.ts
Normal file
69
src/features/rapor/types/rapor.ts
Normal 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;
|
||||
}
|
||||
@@ -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`;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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ı açı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'ı
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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(
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 };
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 =
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
});
|
||||
@@ -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",
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
}
|
||||
@@ -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
78
src/types/yokatlas.ts
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user