import { and, eq, lt } from "drizzle-orm"; import { appDb, schema } from "./appdb"; import { uniqueIhlaliMi } from "./credits"; const { reportLocks } = schema; /** * Bayat kilit eşiği. Tek Yapay Zeka çağrısının sunucu sınırı 60 sn ve üretim * iki denemeye kadar gidebiliyor (bkz. lib/ai/cagri.ts), üstüne havuz SQL'i ve * DB yazımı biniyor. 180 sn bu üst sınırın rahatça üstünde: bu kadar eski bir * kilit ancak süreç öldüyse (OOM, deploy) kalmıştır ve devralınabilir. */ const BAYAT_MS = 180_000; export type KilitSonuc = | { ok: true } /** Aynı kullanıcı için taze bir üretim zaten koşuyor. */ | { ok: false; error: "SURUYOR" }; /** * Kullanıcı başına üretim kilidini alır. * * Neden var: `credit_ledger.ref_id` benzersizliği yalnız AYNI denemenin * tekrarını koruyor (ağda kaybolan cevap → aynı requestId ile retry). Oysa * `requestId` istemcide her mount'ta yeniden üretiliyor, yani ikinci sekmede * açılan `/listem?uret=1` ya da geri→ileri ikinci bir üretim başlatıp 3 krediyi * ikinci kez yakıyordu — tek rapor karşılığında (reports.user_id benzersiz). * * Kilit krediden ÖNCE alınır: harcama yapılmadan reddedilir. */ export async function uretimKilidiAl( userId: string, requestId: string, ): Promise { const simdi = new Date(); try { await appDb .insert(reportLocks) .values({ userId, requestId, startedAt: simdi }); return { ok: true }; } catch (err) { if (!uniqueIhlaliMi(err)) throw err; } // Kilit var. Yalnız bayatsa devral — koşullu UPDATE olduğu için iki eşzamanlı // devralma denemesinden yalnız biri satırı günceller (rowsAffected ile ayırt // edilir), yani yarış çifte harcamaya dönüşemez. const devir = await appDb .update(reportLocks) .set({ requestId, startedAt: simdi }) .where( and( eq(reportLocks.userId, userId), lt(reportLocks.startedAt, new Date(simdi.getTime() - BAYAT_MS)), ), ); if (devir.rowsAffected > 0) return { ok: true }; return { ok: false, error: "SURUYOR" }; } /** * Kilidi bırakır. `requestId` koşulu şart: bayat kilidi devralan başka bir * deneme varken, geç uyanan eski sahip onun kilidini silmemeli. */ export async function uretimKilidiBirak( userId: string, requestId: string, ): Promise { await appDb .delete(reportLocks) .where( and(eq(reportLocks.userId, userId), eq(reportLocks.requestId, requestId)), ) // Salım hiçbir zaman asıl akışı kırmamalı: kilit en kötü ihtimalle // BAYAT_MS sonra kendiliğinden devralınabilir hâle gelir. .catch(() => {}); }