fix: bayat dev-mock raporlar tek kapıdan (kullanicininRaporu) ayıklanıyor
All checks were successful
Deploy / deploy (push) Successful in 9m25s

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 <noreply@anthropic.com>
This commit is contained in:
bilalgursen
2026-08-07 12:45:56 +03:00
parent 620e41edae
commit 7518baccfa
10 changed files with 55 additions and 37 deletions

Binary file not shown.

View File

@@ -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" },

View File

@@ -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,

View File

@@ -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 (

View File

@@ -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");

View File

@@ -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,

View File

@@ -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);

View File

@@ -78,6 +78,9 @@ export type RaporKapsam = {
export type RaporSonuc = Omit<z.infer<typeof RaporSchema>, "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,
};
}

View File

@@ -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<DevDurum> {
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,

34
src/lib/rapor-kaydi.ts Normal file
View File

@@ -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;
}