feat: kimlik doğrulama, ödeme, kredi ve AI rapor sihirbazı ekle

- better-auth ile giriş/oturum yönetimi (src/lib/auth, giris sayfası)
- iyzico ödeme entegrasyonu ve paket satın alma akışı
- kredi sistemi ve uygulama veritabanı şeması (drizzle)
- AI destekli rapor üretimi ve tercih sihirbazı
- rapor yazdırma sayfası ve site header/kullanıcı menüsü

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
bilalgursen
2026-07-22 02:13:39 +03:00
parent 7c3f17b9b5
commit cee6200237
64 changed files with 7966 additions and 89 deletions

133
src/lib/rapor-havuzu.ts Normal file
View File

@@ -0,0 +1,133 @@
import {
searchByRank,
type Program,
type PuanTuruKey,
type UniturGrubu,
PUAN_TURLERI,
} from "./db";
import { kategoriEslesir } from "./kategoriler";
export type Dilim = "hayal" | "dengeli" | "garanti";
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
gevsetildi: ("kategori" | "il")[];
}
const HEDEF = 24;
const LIMIT = 60;
function dilimEkle(r: {
hayal: Program[];
dengeli: Program[];
garanti: Program[];
}): AdayProgram[] {
return [
...r.hayal.map((p) => ({ ...p, dilim: "hayal" as const })),
...r.dengeli.map((p) => ({ ...p, dilim: "dengeli" as const })),
...r.garanti.map((p) => ({ ...p, dilim: "garanti" as const })),
];
}
/**
* Deterministik aday havuzu: Claude bu havuzdan SEÇER, asla uydurmaz.
* Sihirbaz seçimlerine (kategori/il/üniversite tipi) göre filtreler; havuz
* 24'ün altına düşerse kademeli gevşetir (önce il, sonra kategori) ki liste
* her zaman kurulabilsin.
*/
export function havuzOlustur(params: RaporParams): HavuzSonuc {
const uniturGrubu =
params.universiteTipi && params.universiteTipi !== "farketmez"
? params.universiteTipi
: undefined;
const kategoriler = params.kategoriler ?? [];
const iller = [
...(params.il ? [params.il] : []),
...(params.iller ?? []),
].filter((v, i, a) => v && a.indexOf(v) === i);
const kategoriFiltre = (havuz: AdayProgram[]) =>
kategoriler.length > 0
? havuz.filter((p) => kategoriEslesir(p.isim, kategoriler))
: havuz;
// 1. Tam filtre: iller + üniversite tipi (SQL), kategori (JS)
const tamHam = dilimEkle(
searchByRank(params.sira, params.tur, {
iller: iller.length > 0 ? iller : undefined,
uniturGrubu,
limitPerBucket: LIMIT,
}),
);
const tamFiltreli = kategoriFiltre(tamHam);
if (tamFiltreli.length >= HEDEF) {
return { havuz: tamFiltreli, gevsetildi: [] };
}
// 2. Kategori gevşet: aynı illerdeki kategori-dışı programlarla doldur
if (kategoriler.length > 0 && tamHam.length >= HEDEF) {
return { havuz: tamHam, gevsetildi: ["kategori"] };
}
// 3. İl gevşet: il filtresini kaldırıp kategoriye geri dön
const gevsetildi: ("kategori" | "il")[] = [];
if (iller.length > 0) gevsetildi.push("il");
const ilsizHam = dilimEkle(
searchByRank(params.sira, params.tur, {
uniturGrubu,
limitPerBucket: LIMIT,
}),
);
const ilsizFiltreli = kategoriFiltre(ilsizHam);
if (ilsizFiltreli.length >= HEDEF) {
return { havuz: ilsizFiltreli, gevsetildi };
}
// 4. Hem il hem kategori gevşet: elde ne varsa
if (kategoriler.length > 0) gevsetildi.push("kategori");
return { havuz: ilsizHam, gevsetildi };
}
/** Prompt'a enjekte edilecek kompakt satır (token tasarrufu). */
export function havuzuKompaktJson(havuz: AdayProgram[]): string {
return JSON.stringify(
havuz.map((p) => ({
id: p.id,
isim: p.isim,
uni: p.universite,
unitur: p.unitur,
il: p.il,
dilim: p.dilim,
sira: {
y2025: p.sira2025,
y2024: p.sira2024,
y2023: p.sira2023,
y2022: p.sira2022,
},
kontenjan2025: p.kontenjan2025,
yerlesen2025: p.yerlesen2025,
})),
);
}
export function turLabel(tur: PuanTuruKey): string {
return PUAN_TURLERI[tur];
}