feat: tadımlık havuzuna üniversite tipi boyutu
Tadımlık satırı batch üretildiği için sihirbazdaki devlet/vakıf seçimini bilmiyor, vakıf seçen kullanıcıya devlet örneği gösteriyordu. Havuz anahtarı kova×tür×kategori×tip'e genişledi (migration 0001; mevcut satırlar tip='genel'). Üretim scripti tip varyantlarını uniturGrubu filtresiyle seçer; tadimlikSec tip verildiğinde kategori→genel fallback'i korur ama tip fallback'i YAPMAZ — uygun satır yoksa bileşen gizlenir, yanlış tipte örnek asla gösterilmez. Client profildeki tipi de gönderir ve SSR "genel" satırı tipe uymuyorsa fetch bitene kadar gizler. Not: prod app.db volume'de — `pnpm tadimlik` prod DB'ye karşı ayrıca koşulmalı. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
3
drizzle/0001_steady_donald_blake.sql
Normal file
3
drizzle/0001_steady_donald_blake.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
DROP INDEX `tadimlik_anahtar`;--> statement-breakpoint
|
||||||
|
ALTER TABLE `tadimlik_havuzu` ADD `tip` text DEFAULT 'genel' NOT NULL;--> statement-breakpoint
|
||||||
|
CREATE UNIQUE INDEX `tadimlik_anahtar` ON `tadimlik_havuzu` (`kova_slug`,`tur`,`kategori_slug`,`tip`);
|
||||||
907
drizzle/meta/0001_snapshot.json
Normal file
907
drizzle/meta/0001_snapshot.json
Normal file
@@ -0,0 +1,907 @@
|
|||||||
|
{
|
||||||
|
"version": "6",
|
||||||
|
"dialect": "sqlite",
|
||||||
|
"id": "46e3e804-4222-4e4c-9151-dd71510cded5",
|
||||||
|
"prevId": "a802fded-01d5-4d17-8b6f-fca5dfc2920d",
|
||||||
|
"tables": {
|
||||||
|
"account": {
|
||||||
|
"name": "account",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"account_id": {
|
||||||
|
"name": "account_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"provider_id": {
|
||||||
|
"name": "provider_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token": {
|
||||||
|
"name": "access_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token": {
|
||||||
|
"name": "refresh_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"id_token": {
|
||||||
|
"name": "id_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"access_token_expires_at": {
|
||||||
|
"name": "access_token_expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"refresh_token_expires_at": {
|
||||||
|
"name": "refresh_token_expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"name": "scope",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"name": "password",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {
|
||||||
|
"account_user_id_user_id_fk": {
|
||||||
|
"name": "account_user_id_user_id_fk",
|
||||||
|
"tableFrom": "account",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"chat_messages": {
|
||||||
|
"name": "chat_messages",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"role": {
|
||||||
|
"name": "role",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"content": {
|
||||||
|
"name": "content",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"client_message_id": {
|
||||||
|
"name": "client_message_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"chat_messages_client_message_id_unique": {
|
||||||
|
"name": "chat_messages_client_message_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"client_message_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"chat_user_created": {
|
||||||
|
"name": "chat_user_created",
|
||||||
|
"columns": [
|
||||||
|
"user_id",
|
||||||
|
"created_at"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"chat_messages_user_id_user_id_fk": {
|
||||||
|
"name": "chat_messages_user_id_user_id_fk",
|
||||||
|
"tableFrom": "chat_messages",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"credit_ledger": {
|
||||||
|
"name": "credit_ledger",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"delta": {
|
||||||
|
"name": "delta",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"reason": {
|
||||||
|
"name": "reason",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ref_id": {
|
||||||
|
"name": "ref_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"ledger_reason_ref": {
|
||||||
|
"name": "ledger_reason_ref",
|
||||||
|
"columns": [
|
||||||
|
"reason",
|
||||||
|
"ref_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
},
|
||||||
|
"ledger_user": {
|
||||||
|
"name": "ledger_user",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"credit_ledger_user_id_user_id_fk": {
|
||||||
|
"name": "credit_ledger_user_id_user_id_fk",
|
||||||
|
"tableFrom": "credit_ledger",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"orders": {
|
||||||
|
"name": "orders",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"product": {
|
||||||
|
"name": "product",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"amount_kurus": {
|
||||||
|
"name": "amount_kurus",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"credits": {
|
||||||
|
"name": "credits",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"name": "status",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'pending'"
|
||||||
|
},
|
||||||
|
"iyzico_token": {
|
||||||
|
"name": "iyzico_token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"iyzico_payment_id": {
|
||||||
|
"name": "iyzico_payment_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"paid_at": {
|
||||||
|
"name": "paid_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"orders_user": {
|
||||||
|
"name": "orders_user",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"orders_user_id_user_id_fk": {
|
||||||
|
"name": "orders_user_id_user_id_fk",
|
||||||
|
"tableFrom": "orders",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"reports": {
|
||||||
|
"name": "reports",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"name": "params",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"result": {
|
||||||
|
"name": "result",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"revision_count": {
|
||||||
|
"name": "revision_count",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"reports_user_id_unique": {
|
||||||
|
"name": "reports_user_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"reports_user_id_user_id_fk": {
|
||||||
|
"name": "reports_user_id_user_id_fk",
|
||||||
|
"tableFrom": "reports",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"saved_lists": {
|
||||||
|
"name": "saved_lists",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
"name": "items",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"profil": {
|
||||||
|
"name": "profil",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"saved_lists_user_id_unique": {
|
||||||
|
"name": "saved_lists_user_id_unique",
|
||||||
|
"columns": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"saved_lists_user_id_user_id_fk": {
|
||||||
|
"name": "saved_lists_user_id_user_id_fk",
|
||||||
|
"tableFrom": "saved_lists",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "no action",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"name": "session",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"token": {
|
||||||
|
"name": "token",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ip_address": {
|
||||||
|
"name": "ip_address",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_agent": {
|
||||||
|
"name": "user_agent",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"user_id": {
|
||||||
|
"name": "user_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"session_token_unique": {
|
||||||
|
"name": "session_token_unique",
|
||||||
|
"columns": [
|
||||||
|
"token"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {
|
||||||
|
"session_user_id_user_id_fk": {
|
||||||
|
"name": "session_user_id_user_id_fk",
|
||||||
|
"tableFrom": "session",
|
||||||
|
"tableTo": "user",
|
||||||
|
"columnsFrom": [
|
||||||
|
"user_id"
|
||||||
|
],
|
||||||
|
"columnsTo": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"onDelete": "cascade",
|
||||||
|
"onUpdate": "no action"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"tadimlik_havuzu": {
|
||||||
|
"name": "tadimlik_havuzu",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"kova_slug": {
|
||||||
|
"name": "kova_slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tur": {
|
||||||
|
"name": "tur",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"kategori_slug": {
|
||||||
|
"name": "kategori_slug",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"tip": {
|
||||||
|
"name": "tip",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": "'genel'"
|
||||||
|
},
|
||||||
|
"program_id": {
|
||||||
|
"name": "program_id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"dilim": {
|
||||||
|
"name": "dilim",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"gerekce": {
|
||||||
|
"name": "gerekce",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"risk_notu": {
|
||||||
|
"name": "risk_notu",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"trend_ozeti": {
|
||||||
|
"name": "trend_ozeti",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"program": {
|
||||||
|
"name": "program",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"ornek_sira": {
|
||||||
|
"name": "ornek_sira",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"uretim_at": {
|
||||||
|
"name": "uretim_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"tadimlik_anahtar": {
|
||||||
|
"name": "tadimlik_anahtar",
|
||||||
|
"columns": [
|
||||||
|
"kova_slug",
|
||||||
|
"tur",
|
||||||
|
"kategori_slug",
|
||||||
|
"tip"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"name": "user",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"name": "name",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"name": "email",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"email_verified": {
|
||||||
|
"name": "email_verified",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"image": {
|
||||||
|
"name": "image",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"credit_balance": {
|
||||||
|
"name": "credit_balance",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": 0
|
||||||
|
},
|
||||||
|
"has_paket": {
|
||||||
|
"name": "has_paket",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"kredi_bitti_at": {
|
||||||
|
"name": "kredi_bitti_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"kredi_hatirlatma_gonderildi_at": {
|
||||||
|
"name": "kredi_hatirlatma_gonderildi_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {
|
||||||
|
"user_email_unique": {
|
||||||
|
"name": "user_email_unique",
|
||||||
|
"columns": [
|
||||||
|
"email"
|
||||||
|
],
|
||||||
|
"isUnique": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
},
|
||||||
|
"verification": {
|
||||||
|
"name": "verification",
|
||||||
|
"columns": {
|
||||||
|
"id": {
|
||||||
|
"name": "id",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": true,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"identifier": {
|
||||||
|
"name": "identifier",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"name": "value",
|
||||||
|
"type": "text",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"expires_at": {
|
||||||
|
"name": "expires_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": true,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"name": "created_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"name": "updated_at",
|
||||||
|
"type": "integer",
|
||||||
|
"primaryKey": false,
|
||||||
|
"notNull": false,
|
||||||
|
"autoincrement": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"indexes": {},
|
||||||
|
"foreignKeys": {},
|
||||||
|
"compositePrimaryKeys": {},
|
||||||
|
"uniqueConstraints": {},
|
||||||
|
"checkConstraints": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"views": {},
|
||||||
|
"enums": {},
|
||||||
|
"_meta": {
|
||||||
|
"schemas": {},
|
||||||
|
"tables": {},
|
||||||
|
"columns": {}
|
||||||
|
},
|
||||||
|
"internal": {
|
||||||
|
"indexes": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,13 @@
|
|||||||
"when": 1786053613118,
|
"when": 1786053613118,
|
||||||
"tag": "0000_baslangic",
|
"tag": "0000_baslangic",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 1,
|
||||||
|
"version": "6",
|
||||||
|
"when": 1786223252523,
|
||||||
|
"tag": "0001_steady_donald_blake",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* Tadımlık havuzu batch üretimi: kova × puan türü × (kategori + "genel")
|
* Tadımlık havuzu batch üretimi: kova × puan türü × (kategori + "genel") ×
|
||||||
* kombinasyonları için 1'er örnek tercih satırı (gerekce/riskNotu/trendOzeti)
|
* (üniversite tipi: genel/devlet/vakıf) kombinasyonları için 1'er örnek
|
||||||
* üretip app.db'deki tadimlik_havuzu tablosuna yazar.
|
* tercih satırı (gerekce/riskNotu/trendOzeti) üretip app.db'deki
|
||||||
|
* tadimlik_havuzu tablosuna yazar.
|
||||||
*
|
*
|
||||||
* pnpm tadimlik # boş anahtarları doldurur (idempotent)
|
* pnpm tadimlik # boş anahtarları doldurur (idempotent)
|
||||||
* pnpm tadimlik --force # mevcut satırları da yeniden üretir
|
* pnpm tadimlik --force # mevcut satırları da yeniden üretir
|
||||||
* pnpm tadimlik --kova=10-50k --tur=say --kategori=muhendislik-teknoloji
|
* pnpm tadimlik --kova=10-50k --tur=say --kategori=muhendislik-teknoloji
|
||||||
|
* pnpm tadimlik --tip=vakif # yalnızca vakıf varyantları
|
||||||
* pnpm tadimlik --mock # anahtar olsa da mock metin (dev/test)
|
* pnpm tadimlik --mock # anahtar olsa da mock metin (dev/test)
|
||||||
* pnpm tadimlik --bekleme=13000 # LLM çağrıları arası bekleme (ms)
|
* pnpm tadimlik --bekleme=13000 # LLM çağrıları arası bekleme (ms)
|
||||||
*
|
*
|
||||||
@@ -24,10 +26,16 @@ import {
|
|||||||
type DilimKey,
|
type DilimKey,
|
||||||
type Program,
|
type Program,
|
||||||
type PuanTuruKey,
|
type PuanTuruKey,
|
||||||
|
type UniturGrubu,
|
||||||
} from "../src/types/yokatlas";
|
} from "../src/types/yokatlas";
|
||||||
import { KATEGORILER, kategoriEslesir } from "../src/lib/kategoriler";
|
import { KATEGORILER, kategoriEslesir } from "../src/lib/kategoriler";
|
||||||
import { havuzuKompaktJson, turLabel } from "../src/lib/rapor-havuzu";
|
import { havuzuKompaktJson, turLabel } from "../src/lib/rapor-havuzu";
|
||||||
import { GENEL_KATEGORI, KOVALAR, kategoriSlugBul } from "../src/lib/tadimlik-havuzu";
|
import {
|
||||||
|
GENEL_KATEGORI,
|
||||||
|
KOVALAR,
|
||||||
|
kategoriSlugBul,
|
||||||
|
type TadimlikTip,
|
||||||
|
} from "../src/lib/tadimlik-havuzu";
|
||||||
import { anahtarVar } from "../src/lib/ai/client";
|
import { anahtarVar } from "../src/lib/ai/client";
|
||||||
import { yapilandirilmisUret } from "../src/lib/ai/cagri";
|
import { yapilandirilmisUret } from "../src/lib/ai/cagri";
|
||||||
|
|
||||||
@@ -95,18 +103,22 @@ function programBilgisi(p: Program) {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Deterministik program seçimi: kovanın temsili sırası için "dengeli" dilimden
|
* Deterministik program seçimi: kovanın temsili sırası için "dengeli" dilimden
|
||||||
* (boşsa "garanti") kategoriye uyan, 2025'te en çok yerleşen alan program.
|
* (boşsa "garanti") kategoriye ve üniversite tipine uyan, 2025'te en çok
|
||||||
* Tanınır/dolu programlar tadımlıkta daha ikna edici.
|
* yerleşen alan program. Tanınır/dolu programlar tadımlıkta daha ikna edici.
|
||||||
*/
|
*/
|
||||||
function programSec(
|
function programSec(
|
||||||
temsiliSira: number,
|
temsiliSira: number,
|
||||||
tur: PuanTuruKey,
|
tur: PuanTuruKey,
|
||||||
kategori: string | null,
|
kategori: string | null,
|
||||||
|
uniturGrubu?: UniturGrubu,
|
||||||
): { program: Program; dilim: DilimKey } | null {
|
): { program: Program; dilim: DilimKey } | null {
|
||||||
for (const dilim of ["dengeli", "garanti"] as const) {
|
for (const dilim of ["dengeli", "garanti"] as const) {
|
||||||
const { programlar } = dilimAra(temsiliSira, tur, dilim, { limit: 60 });
|
const { programlar } = dilimAra(temsiliSira, tur, dilim, {
|
||||||
|
limit: 60,
|
||||||
|
uniturGrubu,
|
||||||
|
});
|
||||||
const uygunlar = kategori
|
const uygunlar = kategori
|
||||||
? programlar.filter((p) => kategoriEslesir(p.isim, [kategori]))
|
? programlar.filter((p) => kategoriEslesir(p.isim, [kategori], p.fakulte))
|
||||||
: programlar;
|
: programlar;
|
||||||
if (uygunlar.length === 0) continue;
|
if (uygunlar.length === 0) continue;
|
||||||
const secilen = [...uygunlar].sort(
|
const secilen = [...uygunlar].sort(
|
||||||
@@ -152,6 +164,7 @@ async function main() {
|
|||||||
const kovaFiltre = bayrak("kova");
|
const kovaFiltre = bayrak("kova");
|
||||||
const turFiltre = bayrak("tur");
|
const turFiltre = bayrak("tur");
|
||||||
const kategoriFiltre = bayrak("kategori");
|
const kategoriFiltre = bayrak("kategori");
|
||||||
|
const tipFiltre = bayrak("tip");
|
||||||
|
|
||||||
const turlar = (Object.keys(PUAN_TURLERI) as PuanTuruKey[]).filter(
|
const turlar = (Object.keys(PUAN_TURLERI) as PuanTuruKey[]).filter(
|
||||||
(t) => !turFiltre || t === turFiltre,
|
(t) => !turFiltre || t === turFiltre,
|
||||||
@@ -161,6 +174,9 @@ async function main() {
|
|||||||
...KATEGORILER.map((ad) => ({ ad: ad as string, slug: kategoriSlugBul(ad) })),
|
...KATEGORILER.map((ad) => ({ ad: ad as string, slug: kategoriSlugBul(ad) })),
|
||||||
].filter((k) => !kategoriFiltre || k.slug === kategoriFiltre);
|
].filter((k) => !kategoriFiltre || k.slug === kategoriFiltre);
|
||||||
const kovalar = KOVALAR.filter((k) => !kovaFiltre || k.slug === kovaFiltre);
|
const kovalar = KOVALAR.filter((k) => !kovaFiltre || k.slug === kovaFiltre);
|
||||||
|
const tipler = (["genel", "devlet", "vakif"] as TadimlikTip[]).filter(
|
||||||
|
(t) => !tipFiltre || t === tipFiltre,
|
||||||
|
);
|
||||||
|
|
||||||
const mevcutlar = new Set(
|
const mevcutlar = new Set(
|
||||||
(
|
(
|
||||||
@@ -169,9 +185,10 @@ async function main() {
|
|||||||
kovaSlug: schema.tadimlikHavuzu.kovaSlug,
|
kovaSlug: schema.tadimlikHavuzu.kovaSlug,
|
||||||
tur: schema.tadimlikHavuzu.tur,
|
tur: schema.tadimlikHavuzu.tur,
|
||||||
kategoriSlug: schema.tadimlikHavuzu.kategoriSlug,
|
kategoriSlug: schema.tadimlikHavuzu.kategoriSlug,
|
||||||
|
tip: schema.tadimlikHavuzu.tip,
|
||||||
})
|
})
|
||||||
.from(schema.tadimlikHavuzu)
|
.from(schema.tadimlikHavuzu)
|
||||||
).map((s) => `${s.kovaSlug}|${s.tur}|${s.kategoriSlug}`),
|
).map((s) => `${s.kovaSlug}|${s.tur}|${s.kategoriSlug}|${s.tip}`),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (mockAktif()) {
|
if (mockAktif()) {
|
||||||
@@ -185,55 +202,46 @@ async function main() {
|
|||||||
for (const kova of kovalar) {
|
for (const kova of kovalar) {
|
||||||
for (const tur of turlar) {
|
for (const tur of turlar) {
|
||||||
for (const kategori of kategoriler) {
|
for (const kategori of kategoriler) {
|
||||||
const anahtar = `${kova.slug}|${tur}|${kategori.slug}`;
|
for (const tip of tipler) {
|
||||||
if (!force && mevcutlar.has(anahtar)) {
|
const anahtar = `${kova.slug}|${tur}|${kategori.slug}|${tip}`;
|
||||||
atlananMevcut++;
|
if (!force && mevcutlar.has(anahtar)) {
|
||||||
continue;
|
atlananMevcut++;
|
||||||
}
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const secim = programSec(kova.temsiliSira, tur, kategori.ad);
|
const secim = programSec(
|
||||||
if (!secim) {
|
kova.temsiliSira,
|
||||||
atlananBos++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const metin = await metinUret(
|
|
||||||
secim.program,
|
|
||||||
secim.dilim,
|
|
||||||
kova.temsiliSira,
|
|
||||||
tur,
|
|
||||||
kategori.ad,
|
|
||||||
);
|
|
||||||
if (!metin) {
|
|
||||||
console.warn(`Şemaya uymayan çıktı, atlandı: ${anahtar}`);
|
|
||||||
atlananBos++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const now = new Date();
|
|
||||||
await appDb
|
|
||||||
.insert(schema.tadimlikHavuzu)
|
|
||||||
.values({
|
|
||||||
id: randomUUID(),
|
|
||||||
kovaSlug: kova.slug,
|
|
||||||
tur,
|
tur,
|
||||||
kategoriSlug: kategori.slug,
|
kategori.ad,
|
||||||
programId: secim.program.id,
|
tip === "genel" ? undefined : tip,
|
||||||
dilim: secim.dilim,
|
);
|
||||||
gerekce: metin.gerekce,
|
if (!secim) {
|
||||||
riskNotu: metin.riskNotu,
|
atlananBos++;
|
||||||
trendOzeti: metin.trendOzeti,
|
continue;
|
||||||
program: programBilgisi(secim.program),
|
}
|
||||||
ornekSira: kova.temsiliSira,
|
|
||||||
uretimAt: now,
|
const metin = await metinUret(
|
||||||
})
|
secim.program,
|
||||||
.onConflictDoUpdate({
|
secim.dilim,
|
||||||
target: [
|
kova.temsiliSira,
|
||||||
schema.tadimlikHavuzu.kovaSlug,
|
tur,
|
||||||
schema.tadimlikHavuzu.tur,
|
kategori.ad,
|
||||||
schema.tadimlikHavuzu.kategoriSlug,
|
);
|
||||||
],
|
if (!metin) {
|
||||||
set: {
|
console.warn(`Şemaya uymayan çıktı, atlandı: ${anahtar}`);
|
||||||
|
atlananBos++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
await appDb
|
||||||
|
.insert(schema.tadimlikHavuzu)
|
||||||
|
.values({
|
||||||
|
id: randomUUID(),
|
||||||
|
kovaSlug: kova.slug,
|
||||||
|
tur,
|
||||||
|
kategoriSlug: kategori.slug,
|
||||||
|
tip,
|
||||||
programId: secim.program.id,
|
programId: secim.program.id,
|
||||||
dilim: secim.dilim,
|
dilim: secim.dilim,
|
||||||
gerekce: metin.gerekce,
|
gerekce: metin.gerekce,
|
||||||
@@ -242,15 +250,35 @@ async function main() {
|
|||||||
program: programBilgisi(secim.program),
|
program: programBilgisi(secim.program),
|
||||||
ornekSira: kova.temsiliSira,
|
ornekSira: kova.temsiliSira,
|
||||||
uretimAt: now,
|
uretimAt: now,
|
||||||
},
|
})
|
||||||
});
|
.onConflictDoUpdate({
|
||||||
|
target: [
|
||||||
|
schema.tadimlikHavuzu.kovaSlug,
|
||||||
|
schema.tadimlikHavuzu.tur,
|
||||||
|
schema.tadimlikHavuzu.kategoriSlug,
|
||||||
|
schema.tadimlikHavuzu.tip,
|
||||||
|
],
|
||||||
|
set: {
|
||||||
|
programId: secim.program.id,
|
||||||
|
dilim: secim.dilim,
|
||||||
|
gerekce: metin.gerekce,
|
||||||
|
riskNotu: metin.riskNotu,
|
||||||
|
trendOzeti: metin.trendOzeti,
|
||||||
|
program: programBilgisi(secim.program),
|
||||||
|
ornekSira: kova.temsiliSira,
|
||||||
|
uretimAt: now,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
uretilen++;
|
uretilen++;
|
||||||
console.log(`✓ ${anahtar} → ${secim.program.isim} (${secim.dilim})`);
|
console.log(
|
||||||
// Gerçek LLM'de rate limit dostu ardışık tempo
|
`✓ ${anahtar} → ${secim.program.isim} (${secim.dilim})`,
|
||||||
if (!mockAktif()) {
|
);
|
||||||
const bekleme = Number.parseInt(bayrak("bekleme") ?? "500", 10);
|
// Gerçek LLM'de rate limit dostu ardışık tempo
|
||||||
await new Promise((r) => setTimeout(r, bekleme));
|
if (!mockAktif()) {
|
||||||
|
const bekleme = Number.parseInt(bayrak("bekleme") ?? "500", 10);
|
||||||
|
await new Promise((r) => setTimeout(r, bekleme));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,18 @@ export async function GET(istek: Request) {
|
|||||||
? kategoriParam
|
? kategoriParam
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const tadimlik = await tadimlikSec(sira, tur as PuanTuruKey, kategoriSlug);
|
// Sihirbazdaki devlet/vakıf tercihi: uygun tipte satır yoksa null döner,
|
||||||
|
// yanlış tipte örnek asla servis edilmez.
|
||||||
|
const tipParam = url.searchParams.get("tip");
|
||||||
|
const tip =
|
||||||
|
tipParam === "devlet" || tipParam === "vakif" ? tipParam : "genel";
|
||||||
|
|
||||||
|
const tadimlik = await tadimlikSec(
|
||||||
|
sira,
|
||||||
|
tur as PuanTuruKey,
|
||||||
|
kategoriSlug,
|
||||||
|
tip,
|
||||||
|
);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ tadimlik },
|
{ tadimlik },
|
||||||
// Satırlar kova granülaritesinde statik; 1 saat cache yeterli.
|
// Satırlar kova granülaritesinde statik; 1 saat cache yeterli.
|
||||||
|
|||||||
@@ -27,10 +27,17 @@ import { dilimdenRisk, riskHesapla } from "@/lib/risk";
|
|||||||
import { turkishSlugify } from "@/lib/slug";
|
import { turkishSlugify } from "@/lib/slug";
|
||||||
import { PROFIL_DEGISTI_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
import { PROFIL_DEGISTI_EVENT, sonucHref } from "@/features/sihirbaz/sihirbaz-sabitler";
|
||||||
import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
|
import { tercihProfiliOku } from "@/features/sihirbaz/sihirbaz-profil";
|
||||||
import type { TadimlikSatir } from "@/lib/tadimlik-havuzu";
|
import type { TadimlikSatir, TadimlikTip } from "@/lib/tadimlik-havuzu";
|
||||||
|
|
||||||
const GENEL = "genel";
|
const GENEL = "genel";
|
||||||
|
|
||||||
|
/** Program tipi ile sihirbaz tercihi eşleşiyor mu (uniturFiltreSql semantiği). */
|
||||||
|
function tipeUyar(program: TadimlikSatir["program"], tip: TadimlikTip): boolean {
|
||||||
|
if (tip === "genel") return true;
|
||||||
|
if (tip === "devlet") return program.unitur === "DEVLET";
|
||||||
|
return program.unitur != null && program.unitur !== "DEVLET";
|
||||||
|
}
|
||||||
|
|
||||||
export function TadimlikSatiri({
|
export function TadimlikSatiri({
|
||||||
sira,
|
sira,
|
||||||
tur,
|
tur,
|
||||||
@@ -42,29 +49,46 @@ export function TadimlikSatiri({
|
|||||||
ilk: TadimlikSatir | null;
|
ilk: TadimlikSatir | null;
|
||||||
}) {
|
}) {
|
||||||
const [satir, setSatir] = useState<TadimlikSatir | null>(ilk);
|
const [satir, setSatir] = useState<TadimlikSatir | null>(ilk);
|
||||||
const yuklenenSlug = useRef(GENEL);
|
const yuklenenAnahtar = useRef(`${GENEL}|${GENEL}`);
|
||||||
|
|
||||||
// Sihirbaz kategorisi yalnızca localStorage'da yaşar; mount'ta ve profil
|
// Sihirbaz kategorisi ve devlet/vakıf tercihi yalnızca localStorage'da
|
||||||
// değiştikçe okunur, kategoriye özel satır sunucudan çekilir.
|
// yaşar; mount'ta ve profil değiştikçe okunur, seçime özel satır sunucudan
|
||||||
|
// çekilir. Tip tercihi varsa yanlış tipte örnek bir an bile gösterilmez.
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const guncelle = () => {
|
const guncelle = () => {
|
||||||
const profil = tercihProfiliOku();
|
const profil = tercihProfiliOku();
|
||||||
const kategori = profil?.secimler.kategoriler[0];
|
const kategori = profil?.secimler.kategoriler[0];
|
||||||
const slug = kategori ? turkishSlugify(kategori) : GENEL;
|
const slug = kategori ? turkishSlugify(kategori) : GENEL;
|
||||||
if (slug === yuklenenSlug.current) return;
|
const tipTercihi = profil?.secimler.universiteTipi;
|
||||||
yuklenenSlug.current = slug;
|
const tip: TadimlikTip =
|
||||||
if (slug === GENEL) {
|
tipTercihi === "devlet" || tipTercihi === "vakif"
|
||||||
|
? tipTercihi
|
||||||
|
: GENEL;
|
||||||
|
const anahtar = `${slug}|${tip}`;
|
||||||
|
if (anahtar === yuklenenAnahtar.current) return;
|
||||||
|
yuklenenAnahtar.current = anahtar;
|
||||||
|
if (slug === GENEL && tip === GENEL) {
|
||||||
setSatir(ilk);
|
setSatir(ilk);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fetch(
|
// SSR "genel" satırı seçilen tipe uymuyorsa fetch bitene kadar gizle
|
||||||
`/api/tadimlik?sira=${sira}&tur=${encodeURIComponent(tur)}&kategori=${encodeURIComponent(slug)}`,
|
setSatir((mevcut) =>
|
||||||
)
|
mevcut && !tipeUyar(mevcut.program, tip) ? null : mevcut,
|
||||||
|
);
|
||||||
|
const q = new URLSearchParams({ sira: String(sira), tur });
|
||||||
|
if (slug !== GENEL) q.set("kategori", slug);
|
||||||
|
if (tip !== GENEL) q.set("tip", tip);
|
||||||
|
fetch(`/api/tadimlik?${q}`)
|
||||||
.then((yanit) => (yanit.ok ? yanit.json() : null))
|
.then((yanit) => (yanit.ok ? yanit.json() : null))
|
||||||
.then((veri: { tadimlik: TadimlikSatir | null } | null) => {
|
.then((veri: { tadimlik: TadimlikSatir | null } | null) => {
|
||||||
// Yarış koruması: bu arada başka kategori yüklendiyse dokunma
|
// Yarış koruması: bu arada başka seçim yüklendiyse dokunma
|
||||||
if (yuklenenSlug.current !== slug) return;
|
if (yuklenenAnahtar.current !== anahtar) return;
|
||||||
if (veri?.tadimlik) setSatir(veri.tadimlik);
|
if (veri?.tadimlik) {
|
||||||
|
setSatir(veri.tadimlik);
|
||||||
|
} else if (tip !== GENEL) {
|
||||||
|
// Uygun tipte satır yok: yanlış tip göstermektense gizle
|
||||||
|
setSatir(null);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -154,8 +154,9 @@ export const reports = sqliteTable("reports", {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Anonim /sonuc tadımlığı: batch üretilmiş tek tercih satırları.
|
// Anonim /sonuc tadımlığı: batch üretilmiş tek tercih satırları.
|
||||||
// Anahtar: sıra kovası × puan türü × kategori ("genel" = kategorisiz fallback).
|
// Anahtar: sıra kovası × puan türü × kategori × üniversite tipi
|
||||||
// Anonim istek LLM'e asla gitmez; bu tablo salt-okunur servis edilir.
|
// ("genel" = kategorisiz/tipsiz fallback). Anonim istek LLM'e asla gitmez;
|
||||||
|
// bu tablo salt-okunur servis edilir.
|
||||||
// NOT: Yeni tablo/kolon eklerken `pnpm db:generate` ile migration üretilmeli;
|
// NOT: Yeni tablo/kolon eklerken `pnpm db:generate` ile migration üretilmeli;
|
||||||
// prod şeması açılışta yalnızca drizzle/ altındaki migration'larla güncellenir.
|
// prod şeması açılışta yalnızca drizzle/ altındaki migration'larla güncellenir.
|
||||||
export const tadimlikHavuzu = sqliteTable(
|
export const tadimlikHavuzu = sqliteTable(
|
||||||
@@ -165,6 +166,11 @@ export const tadimlikHavuzu = sqliteTable(
|
|||||||
kovaSlug: text("kova_slug").notNull(),
|
kovaSlug: text("kova_slug").notNull(),
|
||||||
tur: text("tur", { enum: ["say", "ea", "soz", "dil", "tyt"] }).notNull(),
|
tur: text("tur", { enum: ["say", "ea", "soz", "dil", "tyt"] }).notNull(),
|
||||||
kategoriSlug: text("kategori_slug").notNull(),
|
kategoriSlug: text("kategori_slug").notNull(),
|
||||||
|
// Sihirbazdaki devlet/vakıf tercihine uyan varyant; "genel" = tip
|
||||||
|
// filtresi olmayan satır (tipsiz kullanıcı bunu görür).
|
||||||
|
tip: text("tip", { enum: ["genel", "devlet", "vakif"] })
|
||||||
|
.notNull()
|
||||||
|
.default("genel"),
|
||||||
programId: text("program_id").notNull(),
|
programId: text("program_id").notNull(),
|
||||||
dilim: text("dilim", { enum: ["hayal", "dengeli", "garanti"] }).notNull(),
|
dilim: text("dilim", { enum: ["hayal", "dengeli", "garanti"] }).notNull(),
|
||||||
gerekce: text("gerekce").notNull(),
|
gerekce: text("gerekce").notNull(),
|
||||||
@@ -178,7 +184,7 @@ export const tadimlikHavuzu = sqliteTable(
|
|||||||
uretimAt: integer("uretim_at", { mode: "timestamp" }).notNull(),
|
uretimAt: integer("uretim_at", { mode: "timestamp" }).notNull(),
|
||||||
},
|
},
|
||||||
(t) => [
|
(t) => [
|
||||||
uniqueIndex("tadimlik_anahtar").on(t.kovaSlug, t.tur, t.kategoriSlug),
|
uniqueIndex("tadimlik_anahtar").on(t.kovaSlug, t.tur, t.kategoriSlug, t.tip),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -46,15 +46,21 @@ export interface TadimlikSatir {
|
|||||||
program: RaporSonuc["programlar"][string];
|
program: RaporSonuc["programlar"][string];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type TadimlikTip = "genel" | "devlet" | "vakif";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Kova × tür × kategori anahtarıyla tadımlık satırı seçer. Kategoriye özel
|
* Kova × tür × kategori × tip anahtarıyla tadımlık satırı seçer. Kategoriye
|
||||||
* satır yoksa "genel"e düşer; o da yoksa null — çağıran taraf bileşeni hiç
|
* özel satır yoksa "genel" kategoriye düşer; o da yoksa null — çağıran taraf
|
||||||
* render etmez (boş/generic tadımlık gösterilmez, PRD kuralı).
|
* bileşeni hiç render etmez (boş/generic tadımlık gösterilmez, PRD kuralı).
|
||||||
|
*
|
||||||
|
* Tip fallback'i YOKTUR: kullanıcı devlet/vakıf seçtiyse yanlış tipte örnek
|
||||||
|
* asla gösterilmez — uygun satır yoksa tadımlık gizlenir.
|
||||||
*/
|
*/
|
||||||
export async function tadimlikSec(
|
export async function tadimlikSec(
|
||||||
sira: number,
|
sira: number,
|
||||||
tur: PuanTuruKey,
|
tur: PuanTuruKey,
|
||||||
kategoriSlug?: string,
|
kategoriSlug?: string,
|
||||||
|
tip: TadimlikTip = "genel",
|
||||||
): Promise<TadimlikSatir | null> {
|
): Promise<TadimlikSatir | null> {
|
||||||
const kova = kovaBul(sira);
|
const kova = kovaBul(sira);
|
||||||
const sluglar = [
|
const sluglar = [
|
||||||
@@ -66,6 +72,7 @@ export async function tadimlikSec(
|
|||||||
eq(schema.tadimlikHavuzu.kovaSlug, kova.slug),
|
eq(schema.tadimlikHavuzu.kovaSlug, kova.slug),
|
||||||
eq(schema.tadimlikHavuzu.tur, tur),
|
eq(schema.tadimlikHavuzu.tur, tur),
|
||||||
eq(schema.tadimlikHavuzu.kategoriSlug, slug),
|
eq(schema.tadimlikHavuzu.kategoriSlug, slug),
|
||||||
|
eq(schema.tadimlikHavuzu.tip, tip),
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
if (satir) {
|
if (satir) {
|
||||||
|
|||||||
Reference in New Issue
Block a user