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,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";
|
||||
|
||||
Reference in New Issue
Block a user