From 7518baccfa171b19597d8fee08eb900ab06fd937 Mon Sep 17 00:00:00 2001 From: bilalgursen Date: Fri, 7 Aug 2026 12:45:56 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20bayat=20dev-mock=20raporlar=20tek=20kap?= =?UTF-8?q?=C4=B1dan=20(kullanicininRaporu)=20ay=C4=B1klan=C4=B1yor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Dev'de API anahtarı yokken üretilen mock rapor DB'ye kaydediliyordu; anahtar sonradan eklenince kullanıcı başına tek rapor kuralı yüzünden o bayat mock gerçek AI çıktısıymış gibi servis edilmeye devam ediyordu. Rapor tablosunu okuyan her yer (sonuc/listem/yazdir/odeme-sonuc sayfaları, liste action'ları, soru API'si, dev panel) artık src/lib/rapor-kaydi.ts içindeki kullanicininRaporu() kapısından geçiyor: gerçek AI aktifken mock üretimi kayıt "yok" sayılır, kullanıcı gerçek listeyi yeniden üretir. Yeni mock kayıtlar devMock işareti taşır; eskiler metin ön ekinden tanınır. Co-Authored-By: Claude Fable 5 --- data/app.db | Bin 315392 -> 315392 bytes src/app/api/soru/route.ts | 7 +++---- src/app/listem/page.tsx | 5 ++--- src/app/odeme/sonuc/page.tsx | 8 ++------ src/app/rapor/yazdir/page.tsx | 7 ++----- src/app/sonuc/actions.ts | 13 ++++--------- src/app/sonuc/page.tsx | 8 ++------ src/lib/ai/rapor.ts | 4 ++++ src/lib/dev/actions.ts | 6 ++---- src/lib/rapor-kaydi.ts | 34 ++++++++++++++++++++++++++++++++++ 10 files changed, 55 insertions(+), 37 deletions(-) create mode 100644 src/lib/rapor-kaydi.ts diff --git a/data/app.db b/data/app.db index 64ac0606aa27bd2ffdbe257f3c4de04d11718e0a..e280c714eb439818b24bf4e4c8e8bf4ee37b512c 100644 GIT binary patch delta 1380 zcmaiz%Z}3q7=`7j)Qdn9QL46-3Q$Tnh#)(OZ!C%&J9g~Eah%xkWs~}z_!2w5#CD`I zELwI*pjnJmP=$or1v@kqvqe=O0Cm-F+P*=b0My|LW^uOP(SQDP?mfMD@9D*dmw_jb zz9fMsFTVYy@&2Q|kA{1XjxSvMe(&6a2k71f9=*i>03ZJNaeAOF2U1FO+%+ArFx-I= z3Sq!eCX+3Ok6V~Yjxl8+CBoA8homy4*QwZ6w)4#-nxKiaT|n$0W)r20aKV5LJg$yF zTyAG%G~>M_n;KEbk!-(Slc9uEu_z!pst<97!|0vB%%X#glLb`Vh8)YTw(u_Wc4M;F z@Au|jVtWQZwyI8-CNo~evs5!DF$9YGlwH`6Aa4q6F84NQonplbANM%RCy<0(`vROO z&KTBQY^$~8rRl^{m2U84J=#zq78;X@r437PE}IjU0Wl%!`{XWSd$=qi(kPVZfLkV_ zL3-Mdkg*vtwPv(9VAM9h5_wx3mu@@O601G*{BS-CaSv?cox}5&uNSt^;?^+~_@RHRX`vfdd1k9`8EppC9 zCfr?BSxe8A^#pd2kjrD)2~69ZFA|a^%DS>Gua{5)ypSY}f$I&wR#`{dI^7lAM+OyE zS7Vh#TaG!Sg;w0@u2ip6oAxj{u+cEr`*m8)thm*s^0u6HB8Qvd8?J+p!_E!?jm|Wl z#)~-vhZNcg%4HUhf)ydn*aa`mS}8G!5JQ9QjM@b5R1NrOlJk6p@VSa35fmwNzeW0x zn5MP?EvS}($A-Eq!S!U>I;b+*h+PEQ7@_5teIlq?a_2c~c{T8TJ0ul)ro&l@7FCxn zSy2=VM2W`o6srT9k0NA3C`?NW!A)wZjNt|}!?3YV)QDFYmd(b|A}d&Ev&YB7OV8g>ai_QSx?68M)}gMy zi3UE!PeHZm4m7L|g@$N3XsB z9)EDkeaAj|^BK_i@O0Y2zW<#6od4m|D-WMuIlcir{`R9D z@c8F1emHpjc<-&z-tpPx+h6Uy{Y_`@GKXE`zQ#_Tytg`<`A%#tiaFaE!X&(jNWC|j z@gAK(7D6pTM=@4wpTPSl$jhZf%tL)l%K_h>>-Ba!cD#BzGh%8^LVQ?wmNv=cKvb)c zBj-(}GhOrr-3sR&#O`cmlVX{+j6it?vY}p&s+kfW6&UKOiC>s-wI)M})5}3-l}s3g zGpQ&l1=JkJz?&8`_vsd5tR_NF#)%|B7n($naU^I1J11N?(C7jR7MeRQd@)JNn0L3j zD(6(jLg^T?=tZAMd1W^}AsIoJ!W^!oQkGE_OB+EfzZ?u`LkUD0wI9)LIS6yx!-}@bGFcsg>1Ke!Hm7#5 zxbNly<0%VwlT%Zd$Wd3THieXvhLe${r6gI25BMMl(~J_9OAnfIG{(&E!cs<@Qg&A2 zdL5S6ZNDf^{_E5(Jw2h?B5SQ?gHo93lvK$@df)c3_z2+*j=L7asrzZ(iE}<=}k(pZ%8yA0G^N$^3r*&Yjmd;3CQMzwZM79ei?;>i#R% z>D>>3+vhJn2Y$bQKQ~(iOVhjn2kVJ!1q9me;aqGlqjI>Fo6>StdsMSqCiA{fRhl_6 zkr67*T5g8r%YZ(8`2hIrh|+1#WAz}YE6eN0aRv5BZPGFoJ=n6K8}-_OGj?G+4ERA& zMhu4S-fu|T8+_dv&OE!V;i%}jT`t%)Z(5mQEpWcqcQDt1)vi#BX3fz?8mr9O=Pg}I zS}{4Oop}XvHOmflzj4@bHp^h;vz5qF)$=)412TU>~ya oWmxY2v^$6Rs-8XEnR@#okDeU^FYoWY``x4Gz@z7TkDkBwKUx;o1ONa4 diff --git a/src/app/api/soru/route.ts b/src/app/api/soru/route.ts index 0422e29..35e41b2 100644 --- a/src/app/api/soru/route.ts +++ b/src/app/api/soru/route.ts @@ -8,6 +8,7 @@ import { krediBittiIsaretle, } from "@/lib/credits"; import { anahtarVar, SORUMLULUK_REDDI } from "@/lib/ai/client"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { sohbetAkisi } from "@/lib/ai/cagri"; import { listeOzetiCikar, type RaporSonuc } from "@/lib/ai/rapor"; import type { RaporParams } from "@/lib/rapor-havuzu"; @@ -122,10 +123,8 @@ export async function POST(request: NextRequest) { return Response.json({ error: "Geçersiz mesaj." }, { status: 400 }); } - // Soru sormak için önce liste oluşturulmuş olmalı - const rapor = await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, session.user.id), - }); + // Soru sormak için önce liste oluşturulmuş olmalı (bayat dev mock sayılmaz) + const rapor = await kullanicininRaporu(session.user.id); if (!rapor?.result) { return Response.json( { error: "Önce listeni oluşturmalısın.", code: "NO_REPORT" }, diff --git a/src/app/listem/page.tsx b/src/app/listem/page.tsx index a49a1a9..11c20cb 100644 --- a/src/app/listem/page.tsx +++ b/src/app/listem/page.tsx @@ -4,6 +4,7 @@ import { redirect } from "next/navigation"; import { MessageCircleQuestion, Sparkles } from "lucide-react"; import { verifySession, getCurrentUser } from "@/lib/session"; import { appDb, schema } from "@/lib/appdb"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { URUNLER, MAX_REVIZYON } from "@/lib/credits"; import type { RaporSonuc } from "@/lib/ai/rapor"; import type { RaporParams } from "@/lib/rapor-havuzu"; @@ -44,9 +45,7 @@ export default async function ListemPage({ const [user, params, satir, sohbetGecmisi] = await Promise.all([ getCurrentUser(), searchParams, - appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, session.user.id), - }), + kullanicininRaporu(session.user.id), appDb .select({ id: schema.chatMessages.id, diff --git a/src/app/odeme/sonuc/page.tsx b/src/app/odeme/sonuc/page.tsx index 9d97762..0fad9b3 100644 --- a/src/app/odeme/sonuc/page.tsx +++ b/src/app/odeme/sonuc/page.tsx @@ -4,6 +4,7 @@ import { eq } from "drizzle-orm"; import { ArrowRight, CheckCircle2, XCircle } from "lucide-react"; import { verifySession } from "@/lib/session"; import { appDb, schema } from "@/lib/appdb"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { odemeyiSonuclandir } from "@/lib/odeme"; import { Button } from "@/components/ui/button"; import { PagePixelDivider } from "@/components/pixel-decor"; @@ -48,12 +49,7 @@ export default async function OdemeSonucPage({ // Rapor zaten üretildiyse ödül bir tık uzakta: kullanıcıyı doğrudan // rapor sayfasına döndür ki blur çözülme animasyonuyla tam listeyi görsün. - const rapor = basarili - ? await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, session.user.id), - columns: { id: true }, - }) - : null; + const rapor = basarili ? await kullanicininRaporu(session.user.id) : null; const listeHref = rapor ? "/listem?acildi=1" : null; return ( diff --git a/src/app/rapor/yazdir/page.tsx b/src/app/rapor/yazdir/page.tsx index 0c026e8..d931291 100644 --- a/src/app/rapor/yazdir/page.tsx +++ b/src/app/rapor/yazdir/page.tsx @@ -1,9 +1,8 @@ import type { Metadata } from "next"; -import { eq } from "drizzle-orm"; import Image from "next/image"; import { redirect } from "next/navigation"; import { verifySession, getCurrentUser } from "@/lib/session"; -import { appDb, schema } from "@/lib/appdb"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { PUAN_TURLERI } from "@/lib/db"; import type { RaporSonuc } from "@/lib/ai/rapor"; import type { RaporParams } from "@/lib/rapor-havuzu"; @@ -25,9 +24,7 @@ export default async function YazdirPage() { // Kullanıcı satırı ile rapor sorgusu bağımsız — paralel çöz const [user, satir] = await Promise.all([ getCurrentUser(), - appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, session.user.id), - }), + kullanicininRaporu(session.user.id), ]); if (!user) redirect("/giris"); if (!user.hasPaket) redirect("/paket"); diff --git a/src/app/sonuc/actions.ts b/src/app/sonuc/actions.ts index f978cc3..6004e9c 100644 --- a/src/app/sonuc/actions.ts +++ b/src/app/sonuc/actions.ts @@ -3,6 +3,7 @@ import { and, eq, sql } from "drizzle-orm"; import { getSession, getCurrentUser } from "@/lib/session"; import { appDb, schema } from "@/lib/appdb"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { PUAN_TURLERI, type PuanTuruKey } from "@/lib/db"; import { spendCredits, @@ -145,9 +146,7 @@ export async function listeOlustur(input: { "Önceki denemen başarısız olduğu için kredin iade edilmişti. Tekrar dener misin?", }; } - const mevcut = await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, user.id), - }); + const mevcut = await kullanicininRaporu(user.id); if (mevcut?.result) { const rapor = mevcut.result as RaporSonuc; return { @@ -225,9 +224,7 @@ export async function listeRevize(input: { return { ok: false, code: "HATA", error: "Geçersiz istek." }; } - const rapor = await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, user.id), - }); + const rapor = await kullanicininRaporu(user.id); if (!rapor?.result) { return { ok: false, code: "HATA", error: "Önce bir liste oluşturmalısın." }; } @@ -274,9 +271,7 @@ export async function listeRevize(input: { "Önceki denemen başarısız olduğu için kredin iade edilmişti. Tekrar dener misin?", }; } - const guncel = await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, user.id), - }); + const guncel = await kullanicininRaporu(user.id); if (guncel?.result) { return { ok: true, diff --git a/src/app/sonuc/page.tsx b/src/app/sonuc/page.tsx index 02c2ca7..19967fc 100644 --- a/src/app/sonuc/page.tsx +++ b/src/app/sonuc/page.tsx @@ -1,5 +1,4 @@ import type { Metadata } from "next"; -import { eq } from "drizzle-orm"; import { PUAN_TURLERI, rankWindowFacets, @@ -7,7 +6,7 @@ import { type PuanTuruKey, } from "@/lib/db"; import { getSession } from "@/lib/session"; -import { appDb, schema } from "@/lib/appdb"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { tadimlikSec } from "@/lib/tadimlik-havuzu"; import { SihirbazBolumu } from "./sihirbaz-cagri-karti"; import { TadimlikSatiri } from "./tadimlik-satiri"; @@ -55,10 +54,7 @@ export default async function SonucPage({ // için varlık bilgisi gerekir. const session = await getSession(); const raporSatiri = session - ? await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, session.user.id), - columns: { result: true }, - }) + ? await kullanicininRaporu(session.user.id) : null; const facetler = rankWindowFacets(sira, turKey); diff --git a/src/lib/ai/rapor.ts b/src/lib/ai/rapor.ts index cb15fe7..23e53c1 100644 --- a/src/lib/ai/rapor.ts +++ b/src/lib/ai/rapor.ts @@ -78,6 +78,9 @@ export type RaporKapsam = { export type RaporSonuc = Omit, "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 @@ -284,6 +287,7 @@ function mockRapor( ], programlar, kapsam: kapsamUret(hedef), + devMock: true, }; } diff --git a/src/lib/dev/actions.ts b/src/lib/dev/actions.ts index 9178183..1bd3608 100644 --- a/src/lib/dev/actions.ts +++ b/src/lib/dev/actions.ts @@ -7,6 +7,7 @@ import { eq } from "drizzle-orm"; import { headers } from "next/headers"; import { appDb, schema } from "@/lib/appdb"; +import { kullanicininRaporu } from "@/lib/rapor-kaydi"; import { auth } from "@/lib/auth"; import { getCurrentUser } from "@/lib/session"; import { URUNLER } from "@/lib/credits"; @@ -33,10 +34,7 @@ export async function devDurum(): Promise { devKontrol(); const user = await getCurrentUser(); if (!user) return { girisli: false }; - const rapor = await appDb.query.reports.findFirst({ - where: eq(schema.reports.userId, user.id), - columns: { params: true }, - }); + const rapor = await kullanicininRaporu(user.id); const params = rapor?.params as { sira?: number; tur?: string } | undefined; return { girisli: true, diff --git a/src/lib/rapor-kaydi.ts b/src/lib/rapor-kaydi.ts new file mode 100644 index 0000000..12a856e --- /dev/null +++ b/src/lib/rapor-kaydi.ts @@ -0,0 +1,34 @@ +import { eq } from "drizzle-orm"; +import { appDb, schema } from "@/lib/appdb"; +import { anahtarVar } from "@/lib/ai/client"; +import type { RaporSonuc } from "@/lib/ai/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 +// appDb.query.reports.findFirst kullanma. +// +// Neden: dev'de API anahtarı yokken üretilen mock rapor DB'ye kaydediliyor +// (akışın tamamı anahtarsız test edilebilsin diye — bilinçli). Ama anahtar +// sonradan eklenince kullanıcı başına tek rapor kuralı yüzünden o bayat mock, +// gerçek Yapay Zeka çıktısıymış gibi servis edilmeye devam ediyordu. Gerçek AI +// aktifken mock üretimi bir rapor "yok" sayılır; kullanıcı yeni (gerçek) +// listeyi üretir ve kayıt üzerine yazılır. + +function bayatMockMu(sonuc: RaporSonuc): boolean { + const mockUretimi = + sonuc.devMock === true || + // devMock alanı eklenmeden önce kaydedilmiş mock raporlar + sonuc.genelDegerlendirme?.startsWith("(Dev mock"); + if (!mockUretimi) return false; + const mockHalaAktif = + process.env.NODE_ENV !== "production" && !anahtarVar(); + return !mockHalaAktif; +} + +export async function kullanicininRaporu(userId: string) { + const satir = await appDb.query.reports.findFirst({ + where: eq(schema.reports.userId, userId), + }); + if (satir?.result && bayatMockMu(satir.result as RaporSonuc)) return null; + return satir ?? null; +}