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:
111
src/lib/db.ts
111
src/lib/db.ts
@@ -1,5 +1,6 @@
|
||||
import path from "node:path";
|
||||
import Database from "better-sqlite3";
|
||||
import { KATEGORILER, kategoriBul } from "./kategoriler";
|
||||
|
||||
export type Program = {
|
||||
id: string;
|
||||
@@ -62,20 +63,47 @@ 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")
|
||||
return { sql: "AND unitur IS NOT NULL AND unitur <> ?", args: ["DEVLET"] };
|
||||
return { sql: "", args: [] };
|
||||
}
|
||||
|
||||
export function searchByRank(
|
||||
sira: number,
|
||||
turKey: PuanTuruKey,
|
||||
opts: { il?: string; limitPerBucket?: number } = {}
|
||||
opts: {
|
||||
il?: string;
|
||||
iller?: string[];
|
||||
uniturGrubu?: UniturGrubu;
|
||||
limitPerBucket?: number;
|
||||
} = {}
|
||||
): RankResults {
|
||||
const db = getDb();
|
||||
const tur = PUAN_TURLERI[turKey];
|
||||
const onlisans = turKey === "tyt" ? 1 : 0;
|
||||
const limitPerBucket = opts.limitPerBucket ?? 12;
|
||||
const ilFiltre = opts.il ? "AND il = ?" : "";
|
||||
const ilArgs = opts.il ? [opts.il] : [];
|
||||
|
||||
// il (tekil, geriye uyumlu) veya iller (çoklu) — ikisi de verilirse birleşir
|
||||
const illerListe = [
|
||||
...(opts.il ? [opts.il] : []),
|
||||
...(opts.iller ?? []),
|
||||
].filter((v, i, a) => v && a.indexOf(v) === i);
|
||||
const ilFiltre =
|
||||
illerListe.length > 0
|
||||
? `AND il IN (${illerListe.map(() => "?").join(",")})`
|
||||
: "";
|
||||
const unitur = uniturFiltreSql(opts.uniturGrubu);
|
||||
const ilArgs = [...illerListe, ...unitur.args];
|
||||
|
||||
const base = `FROM programs
|
||||
WHERE tur = ? AND onlisans = ? ${ilFiltre} AND ${EFEKTIF_SIRA} IS NOT NULL
|
||||
WHERE tur = ? AND onlisans = ? ${ilFiltre} ${unitur.sql}
|
||||
AND ${EFEKTIF_SIRA} IS NOT NULL
|
||||
AND ${EFEKTIF_SIRA} BETWEEN ? AND ?`;
|
||||
|
||||
// hayal: kullanıcıdan daha iyi taban sıralaması (sınıra en yakın olanlar önce)
|
||||
@@ -122,3 +150,78 @@ export function searchByRank(
|
||||
garanti: garanti as Program[],
|
||||
};
|
||||
}
|
||||
|
||||
export type SihirbazFacetleri = {
|
||||
kategoriler: { ad: string; adet: number }[];
|
||||
iller: { il: string; adet: number }[];
|
||||
uniturler: { grup: UniturGrubu; adet: number }[];
|
||||
};
|
||||
|
||||
// Sihirbaz bubble'ları için erişilebilir program penceresi: hayal alt sınırından
|
||||
// (sira*0.5) garanti üst sınırına (sira*3) kadar. Böylece 500B sıradaki aday Tıp
|
||||
// bubble'ı görmez; 5B sıradaki görür.
|
||||
export function rankWindowFacets(
|
||||
sira: number,
|
||||
turKey: PuanTuruKey
|
||||
): SihirbazFacetleri {
|
||||
const db = getDb();
|
||||
const tur = PUAN_TURLERI[turKey];
|
||||
const onlisans = turKey === "tyt" ? 1 : 0;
|
||||
const altSinir = Math.round(sira * 0.5);
|
||||
const ustSinir = Math.round(sira * 3);
|
||||
|
||||
const base = `FROM programs
|
||||
WHERE tur = ? AND onlisans = ? AND ${EFEKTIF_SIRA} IS NOT NULL
|
||||
AND ${EFEKTIF_SIRA} BETWEEN ? AND ?`;
|
||||
|
||||
// isim bazında say → JS'te kategoriye çevir (bir isim çok kategoriye girebilir)
|
||||
const isimSayilari = db
|
||||
.prepare(`SELECT isim, COUNT(*) AS adet ${base} GROUP BY isim`)
|
||||
.all(tur, onlisans, altSinir, ustSinir) as {
|
||||
isim: string;
|
||||
adet: number;
|
||||
}[];
|
||||
|
||||
const kategoriSay = new Map<string, number>();
|
||||
for (const { isim, adet } of isimSayilari) {
|
||||
for (const k of kategoriBul(isim)) {
|
||||
kategoriSay.set(k, (kategoriSay.get(k) ?? 0) + adet);
|
||||
}
|
||||
}
|
||||
const kategoriler = KATEGORILER.map((ad) => ({
|
||||
ad,
|
||||
adet: kategoriSay.get(ad) ?? 0,
|
||||
}))
|
||||
.filter((k) => k.adet >= 3) // gürültüyü ele
|
||||
.sort((a, b) => b.adet - a.adet);
|
||||
|
||||
const iller = (
|
||||
db
|
||||
.prepare(
|
||||
`SELECT il, COUNT(*) AS adet ${base} AND il IS NOT NULL AND il <> ''
|
||||
GROUP BY il ORDER BY adet DESC LIMIT 15`
|
||||
)
|
||||
.all(tur, onlisans, altSinir, ustSinir) as {
|
||||
il: string;
|
||||
adet: number;
|
||||
}[]
|
||||
).filter((r) => r.adet > 0);
|
||||
|
||||
const uniturSatirlari = db
|
||||
.prepare(`SELECT unitur, COUNT(*) AS adet ${base} GROUP BY unitur`)
|
||||
.all(tur, onlisans, altSinir, ustSinir) as {
|
||||
unitur: string | null;
|
||||
adet: number;
|
||||
}[];
|
||||
let devlet = 0;
|
||||
let vakif = 0;
|
||||
for (const { unitur, adet } of uniturSatirlari) {
|
||||
if (unitur === "DEVLET") devlet += adet;
|
||||
else if (unitur) vakif += adet;
|
||||
}
|
||||
const uniturler: { grup: UniturGrubu; adet: number }[] = [];
|
||||
if (devlet > 0) uniturler.push({ grup: "devlet", adet: devlet });
|
||||
if (vakif > 0) uniturler.push({ grup: "vakif", adet: vakif });
|
||||
|
||||
return { kategoriler, iller, uniturler };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user