Update package.json scripts, enhance layout with new components, and improve API error handling
Some checks failed
Deploy / deploy (push) Failing after 1h28m6s

- Added new scripts for database operations and temporary production in package.json.
- Integrated KaydetBannerLazy component into the layout for improved user notifications.
- Enhanced API error handling in the soru route to mark credit exhaustion.
- Updated the IletisimPage for better button styling and user experience.
- Refactored ListemPage to streamline user flow and improve session handling.
- Removed unused ListePaneli component to clean up the codebase.
This commit is contained in:
bilalgursen
2026-08-06 02:27:42 +03:00
parent 488845ae1f
commit e62c6f07df
77 changed files with 4132 additions and 1659 deletions

View File

@@ -18,6 +18,13 @@ export const user = sqliteTable("user", {
image: text("image"),
creditBalance: integer("credit_balance").notNull().default(0),
hasPaket: integer("has_paket", { mode: "boolean" }).notNull().default(false),
// Kredi-bitti hatırlatma durum makinesi (kuruldu → gönderildi):
// ilk INSUFFICIENT anı; kredi yüklenince sıfırlanır (hatırlatma iptal).
krediBittiAt: integer("kredi_bitti_at", { mode: "timestamp" }),
// Hatırlatma e-postası bir kez gönderilir, bu alan hiç sıfırlanmaz.
krediHatirlatmaGonderildiAt: integer("kredi_hatirlatma_gonderildi_at", {
mode: "timestamp",
}),
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
});
@@ -145,3 +152,47 @@ export const reports = sqliteTable("reports", {
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
});
// Anonim /sonuc tadımlığı: batch üretilmiş tek tercih satırları.
// Anahtar: sıra kovası × puan türü × kategori ("genel" = kategorisiz fallback).
// Anonim istek LLM'e asla gitmez; bu tablo salt-okunur servis edilir.
// NOT: Yeni tablo/kolon eklerken sema-guvence.ts'teki DDL de güncellenmeli.
export const tadimlikHavuzu = sqliteTable(
"tadimlik_havuzu",
{
id: text("id").primaryKey(),
kovaSlug: text("kova_slug").notNull(),
tur: text("tur", { enum: ["say", "ea", "soz", "dil", "tyt"] }).notNull(),
kategoriSlug: text("kategori_slug").notNull(),
programId: text("program_id").notNull(),
dilim: text("dilim", { enum: ["hayal", "dengeli", "garanti"] }).notNull(),
gerekce: text("gerekce").notNull(),
riskNotu: text("risk_notu").notNull(),
trendOzeti: text("trend_ozeti").notNull(),
// Üretim anındaki program snapshot'ı (isim, universite, il, unitur,
// efektifSira, siraGecmisi) — render yokatlas sorgusu gerektirmesin diye.
program: text("program", { mode: "json" }).notNull(),
// Üretimde kullanılan temsili aday sırası (kovanın orta noktası).
ornekSira: integer("ornek_sira").notNull(),
uretimAt: integer("uretim_at", { mode: "timestamp" }).notNull(),
},
(t) => [
uniqueIndex("tadimlik_anahtar").on(t.kovaSlug, t.tur, t.kategoriSlug),
],
);
// Manuel liste + sihirbaz profili — girişte localStorage'dan merge edilir
// (çakışmada client kazanır, 24 sınırı korunur). reports ile aynı upsert deseni.
export const savedLists = sqliteTable("saved_lists", {
id: text("id").primaryKey(),
userId: text("user_id")
.notNull()
.unique()
.references(() => user.id),
// ManuelTercih[] (≤24, sıra korunur)
items: text("items", { mode: "json" }).notNull(),
// TercihProfili | null
profil: text("profil", { mode: "json" }),
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
});

View File

@@ -0,0 +1,45 @@
import { sql } from "drizzle-orm";
import { appDb } from "./index";
/**
* Prod volume'ündeki app.db'ye sonradan eklenen tabloları güvenceye alır.
*
* Projede drizzle migration akışı yok: şema lokalde `pnpm db:push` ile
* uygulanıp data/app.db seed olarak taşınıyor; ama docker-entrypoint.sh
* app.db'yi yalnızca volume BOŞKEN kopyalar — mevcut prod volume'üne yeni
* tablolar başka türlü gelmez. Burası sunucu açılışında (instrumentation)
* bir kez koşar; yalnızca additive DDL içerir, idempotenttir.
*
* NOT: schema.ts'e tablo/kolon eklendiğinde buradaki DDL de güncellenmeli.
*/
export async function semayiGuvenceyeAl(): Promise<void> {
await appDb.run(sql`
CREATE TABLE IF NOT EXISTS tadimlik_havuzu (
id TEXT PRIMARY KEY,
kova_slug TEXT NOT NULL,
tur TEXT NOT NULL,
kategori_slug TEXT NOT NULL,
program_id TEXT NOT NULL,
dilim TEXT NOT NULL,
gerekce TEXT NOT NULL,
risk_notu TEXT NOT NULL,
trend_ozeti TEXT NOT NULL,
program TEXT NOT NULL,
ornek_sira INTEGER NOT NULL,
uretim_at INTEGER NOT NULL
)
`);
await appDb.run(
sql`CREATE UNIQUE INDEX IF NOT EXISTS tadimlik_anahtar ON tadimlik_havuzu (kova_slug, tur, kategori_slug)`,
);
await appDb.run(sql`
CREATE TABLE IF NOT EXISTS saved_lists (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL UNIQUE REFERENCES user(id),
items TEXT NOT NULL,
profil TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
)
`);
}