fix: 로또 API 504 타임아웃 및 Application error 수정
- _nas.ts: AbortSignal timeout 10s → 25s (NAS 무거운 연산 대응)
- stats/performance, report/latest, report/history: maxDuration = 60 추가 (Vercel 함수 타임아웃 연장)
- ReportTab: 에러 응답({error:"NAS_TIMEOUT"}) 받을 시 렌더 전 차단, confidence_factors null guard 추가
- PurchaseTab: API 에러 응답 감지 후 조용히 빈 상태 유지
- PatternTab: 에러 응답 감지 후 에러 메시지 표시
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,9 @@ function nasBase() {
|
|||||||
return base;
|
return base;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function nasGet(path: string): Promise<unknown> {
|
export async function nasGet(path: string, timeoutMs = 25000): Promise<unknown> {
|
||||||
const res = await fetch(`${nasBase()}${path}`, {
|
const res = await fetch(`${nasBase()}${path}`, {
|
||||||
headers: nasHeaders(), signal: AbortSignal.timeout(10000),
|
headers: nasHeaders(), signal: AbortSignal.timeout(timeoutMs),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
@@ -28,7 +28,7 @@ export async function nasPost(path: string, body: unknown): Promise<unknown> {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { ...nasHeaders(), 'Content-Type': 'application/json' },
|
headers: { ...nasHeaders(), 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
signal: AbortSignal.timeout(10000),
|
signal: AbortSignal.timeout(25000),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
@@ -39,7 +39,7 @@ export async function nasPut(path: string, body: unknown): Promise<unknown> {
|
|||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
headers: { ...nasHeaders(), 'Content-Type': 'application/json' },
|
headers: { ...nasHeaders(), 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(body),
|
body: JSON.stringify(body),
|
||||||
signal: AbortSignal.timeout(10000),
|
signal: AbortSignal.timeout(25000),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
@@ -47,7 +47,7 @@ export async function nasPut(path: string, body: unknown): Promise<unknown> {
|
|||||||
|
|
||||||
export async function nasDelete(path: string): Promise<unknown> {
|
export async function nasDelete(path: string): Promise<unknown> {
|
||||||
const res = await fetch(`${nasBase()}${path}`, {
|
const res = await fetch(`${nasBase()}${path}`, {
|
||||||
method: 'DELETE', headers: nasHeaders(), signal: AbortSignal.timeout(10000),
|
method: 'DELETE', headers: nasHeaders(), signal: AbortSignal.timeout(25000),
|
||||||
});
|
});
|
||||||
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
if (!res.ok) throw new Error(`NAS_${res.status}`);
|
||||||
return res.json();
|
return res.json();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { nasGet, requireSubscription, handleNasError } from '../../_nas';
|
import { nasGet, requireSubscription, handleNasError } from '../../_nas';
|
||||||
|
|
||||||
|
export const maxDuration = 60;
|
||||||
|
|
||||||
export async function GET(request: Request) {
|
export async function GET(request: Request) {
|
||||||
try {
|
try {
|
||||||
const auth = await requireSubscription();
|
const auth = await requireSubscription();
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
import { nasGet, requireSubscription, handleNasError } from '../../_nas';
|
import { nasGet, requireSubscription, handleNasError } from '../../_nas';
|
||||||
|
|
||||||
|
export const maxDuration = 60;
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const auth = await requireSubscription();
|
const auth = await requireSubscription();
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import { NextResponse } from 'next/server';
|
|||||||
import { createClient } from '@/lib/supabase/server';
|
import { createClient } from '@/lib/supabase/server';
|
||||||
import { nasGet, handleNasError } from '../../_nas';
|
import { nasGet, handleNasError } from '../../_nas';
|
||||||
|
|
||||||
|
export const maxDuration = 60;
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
try {
|
try {
|
||||||
const supabase = await createClient();
|
const supabase = await createClient();
|
||||||
|
|||||||
@@ -65,7 +65,10 @@ export default function PatternTab() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/lotto/analysis/personal').then(r => r.json())
|
fetch('/api/lotto/analysis/personal').then(r => r.json())
|
||||||
.then(setData)
|
.then(d => {
|
||||||
|
if (d?.error) { setError(d.error === 'NAS_TIMEOUT' ? 'NAS 서버 응답 시간 초과.' : '패턴 분석을 불러오지 못했습니다.'); return; }
|
||||||
|
setData(d);
|
||||||
|
})
|
||||||
.catch(() => setError('패턴 분석을 불러오지 못했습니다.'))
|
.catch(() => setError('패턴 분석을 불러오지 못했습니다.'))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
|
|||||||
@@ -49,9 +49,10 @@ export default function PurchaseTab() {
|
|||||||
fetch('/api/lotto/purchase').then(r => r.json()),
|
fetch('/api/lotto/purchase').then(r => r.json()),
|
||||||
fetch('/api/lotto/purchase/stats').then(r => r.json()),
|
fetch('/api/lotto/purchase/stats').then(r => r.json()),
|
||||||
]);
|
]);
|
||||||
|
if (recRes?.error || statRes?.error) throw new Error(recRes?.error ?? statRes?.error);
|
||||||
setRecords(recRes.records ?? []);
|
setRecords(recRes.records ?? []);
|
||||||
setStats(statRes);
|
setStats(statRes);
|
||||||
} finally { setLoading(false); }
|
} catch { /* 에러 시 빈 상태 유지 */ } finally { setLoading(false); }
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => { load(); }, []);
|
useEffect(() => { load(); }, []);
|
||||||
|
|||||||
@@ -78,8 +78,14 @@ export default function ReportTab() {
|
|||||||
fetch('/api/lotto/report/latest').then(r => r.json()),
|
fetch('/api/lotto/report/latest').then(r => r.json()),
|
||||||
fetch('/api/lotto/report/history?limit=10').then(r => r.json()),
|
fetch('/api/lotto/report/history?limit=10').then(r => r.json()),
|
||||||
]).then(([rep, hist]) => {
|
]).then(([rep, hist]) => {
|
||||||
|
if (rep?.error) {
|
||||||
|
setError(rep.error === 'NAS_TIMEOUT'
|
||||||
|
? 'NAS 서버 응답 시간 초과. 잠시 후 다시 시도해주세요.'
|
||||||
|
: '리포트를 불러오지 못했습니다. (' + rep.error + ')');
|
||||||
|
return;
|
||||||
|
}
|
||||||
setReport(rep);
|
setReport(rep);
|
||||||
setHistory(hist.reports ?? []);
|
setHistory(hist?.reports ?? []);
|
||||||
}).catch(() => setError('리포트를 불러오지 못했습니다.'))
|
}).catch(() => setError('리포트를 불러오지 못했습니다.'))
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -101,7 +107,7 @@ export default function ReportTab() {
|
|||||||
<div style={{ textAlign: 'center', padding: '4rem 0', color: '#f87171', fontSize: '.85rem' }}>{error}</div>
|
<div style={{ textAlign: 'center', padding: '4rem 0', color: '#f87171', fontSize: '.85rem' }}>{error}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!report) return null;
|
if (!report || !report.confidence_factors || !report.recommended_sets) return null;
|
||||||
|
|
||||||
const strategyColors = ['#fbbf24', '#60a5fa', '#a78bfa'];
|
const strategyColors = ['#fbbf24', '#60a5fa', '#a78bfa'];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user