diff --git a/app/api/lotto/analysis/personal/route.ts b/app/api/lotto/analysis/personal/route.ts index ecea060..dd0b55e 100644 --- a/app/api/lotto/analysis/personal/route.ts +++ b/app/api/lotto/analysis/personal/route.ts @@ -1,97 +1,13 @@ import { NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; -import { requireSubscription } from '../../_nas'; +import { nasGet, requireSubscription, handleNasError } from '../../_nas'; -const DRAW_AVG_ODD = 3.0; // 로또 실제 홀수 평균 -const DRAW_AVG_SUM = 138; // 로또 실제 합계 평균 +export const maxDuration = 60; export async function GET() { try { const auth = await requireSubscription(); if (auth instanceof NextResponse) return auth; - - const supabase = await createClient(); - const { data, error } = await supabase - .from('lotto_history') - .select('numbers') - .eq('user_id', auth.userId) - .order('created_at', { ascending: false }) - .limit(500); - - if (error) throw error; - - const rows = data ?? []; - if (rows.length === 0) { - return NextResponse.json({ - total_analyzed: 0, - number_frequency: {}, - top_picks: [], - least_picks: [], - pattern: { avg_odd_count: 0, avg_sum: 0, avg_range: 0, consecutive_rate: 0, zone_avg: {} }, - vs_draw_avg: { odd_diff: 0, sum_diff: 0, odd_tendency: '보통', sum_tendency: '보통' }, - }); - } - - // 번호 빈도 집계 - const freq: Record = {}; - for (let i = 1; i <= 45; i++) freq[i] = 0; - - let totalOdd = 0, totalSum = 0, totalRange = 0, consecutiveCount = 0; - const zoneCounts: Record = { '1-10': 0, '11-20': 0, '21-30': 0, '31-40': 0, '41-45': 0 }; - - for (const row of rows) { - const nums: number[] = Array.isArray(row.numbers) ? row.numbers : []; - const sorted = [...nums].sort((a, b) => a - b); - - for (const n of sorted) { - freq[n] = (freq[n] ?? 0) + 1; - if (n <= 10) zoneCounts['1-10']++; - else if (n <= 20) zoneCounts['11-20']++; - else if (n <= 30) zoneCounts['21-30']++; - else if (n <= 40) zoneCounts['31-40']++; - else zoneCounts['41-45']++; - } - - totalOdd += sorted.filter(n => n % 2 !== 0).length; - totalSum += sorted.reduce((a, b) => a + b, 0); - totalRange += sorted.length >= 2 ? sorted[sorted.length - 1] - sorted[0] : 0; - if (sorted.some((n, i) => i > 0 && n === sorted[i - 1] + 1)) consecutiveCount++; - } - - const n = rows.length; - const avgOdd = totalOdd / n; - const avgSum = totalSum / n; - - const freqEntries = Object.entries(freq) - .map(([num, cnt]) => ({ num: parseInt(num), cnt })) - .sort((a, b) => b.cnt - a.cnt); - - const zoneAvg: Record = {}; - for (const [zone, count] of Object.entries(zoneCounts)) { - zoneAvg[zone] = Math.round((count / n) * 10) / 10; - } - - return NextResponse.json({ - total_analyzed: n, - number_frequency: freq, - top_picks: freqEntries.slice(0, 10).map(e => e.num), - least_picks: freqEntries.slice(-10).map(e => e.num).reverse(), - pattern: { - avg_odd_count: Math.round(avgOdd * 10) / 10, - avg_sum: Math.round(avgSum), - avg_range: Math.round(totalRange / n), - consecutive_rate: Math.round((consecutiveCount / n) * 100) / 100, - zone_avg: zoneAvg, - }, - vs_draw_avg: { - odd_diff: Math.round((avgOdd - DRAW_AVG_ODD) * 10) / 10, - sum_diff: Math.round(avgSum - DRAW_AVG_SUM), - odd_tendency: avgOdd > DRAW_AVG_ODD + 0.3 ? '홀수 선호' : avgOdd < DRAW_AVG_ODD - 0.3 ? '짝수 선호' : '보통', - sum_tendency: avgSum > DRAW_AVG_SUM + 10 ? '고합계 선호' : avgSum < DRAW_AVG_SUM - 10 ? '저합계 선호' : '보통', - }, - }); - } catch (err) { - console.error('[analysis/personal]', err); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } + const data = await nasGet('/api/lotto/analysis/personal'); + return NextResponse.json(data); + } catch (err) { return handleNasError(err); } } diff --git a/app/api/lotto/purchase/[id]/route.ts b/app/api/lotto/purchase/[id]/route.ts index adaf063..d9ff974 100644 --- a/app/api/lotto/purchase/[id]/route.ts +++ b/app/api/lotto/purchase/[id]/route.ts @@ -1,50 +1,25 @@ import { NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; -import { requireSubscription } from '../../_nas'; +import { nasPut, nasDelete, requireSubscription, handleNasError } from '../../_nas'; + +export const maxDuration = 60; export async function PUT(request: Request, { params }: { params: Promise<{ id: string }> }) { try { const auth = await requireSubscription(); if (auth instanceof NextResponse) return auth; - - const supabase = await createClient(); const { id } = await params; const body = await request.json(); - - const { data, error } = await supabase - .from('lotto_purchases') - .update({ prize: body.prize, note: body.note }) - .eq('id', parseInt(id)) - .eq('user_id', auth.userId) // 본인 데이터만 수정 - .select() - .single(); - - if (error) throw error; + const data = await nasPut(`/api/lotto/purchase/${id}`, body); return NextResponse.json(data); - } catch (err) { - console.error('[purchase PUT]', err); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } + } catch (err) { return handleNasError(err); } } export async function DELETE(_req: Request, { params }: { params: Promise<{ id: string }> }) { try { const auth = await requireSubscription(); if (auth instanceof NextResponse) return auth; - - const supabase = await createClient(); const { id } = await params; - - const { error } = await supabase - .from('lotto_purchases') - .delete() - .eq('id', parseInt(id)) - .eq('user_id', auth.userId); // 본인 데이터만 삭제 - - if (error) throw error; - return NextResponse.json({ ok: true }); - } catch (err) { - console.error('[purchase DELETE]', err); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } + const data = await nasDelete(`/api/lotto/purchase/${id}`); + return NextResponse.json(data); + } catch (err) { return handleNasError(err); } } diff --git a/app/api/lotto/purchase/route.ts b/app/api/lotto/purchase/route.ts index 0a74ccc..b4e9fc8 100644 --- a/app/api/lotto/purchase/route.ts +++ b/app/api/lotto/purchase/route.ts @@ -1,58 +1,28 @@ import { NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; -import { requireSubscription } from '../_nas'; +import { nasGet, nasPost, requireSubscription, handleNasError } from '../_nas'; + +export const maxDuration = 60; export async function GET(request: Request) { try { const auth = await requireSubscription(); if (auth instanceof NextResponse) return auth; - - const supabase = await createClient(); const { searchParams } = new URL(request.url); - const drawNo = searchParams.get('draw_no'); - - let query = supabase - .from('lotto_purchases') - .select('id, draw_no, amount, sets, prize, note, created_at') - .eq('user_id', auth.userId) - .order('draw_no', { ascending: false }); - - if (drawNo) query = query.eq('draw_no', parseInt(drawNo)); - - const { data, error } = await query; - if (error) throw error; - return NextResponse.json({ records: data ?? [] }); - } catch (err) { - console.error('[purchase GET]', err); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } + const params = new URLSearchParams(); + if (searchParams.get('draw_no')) params.set('draw_no', searchParams.get('draw_no')!); + if (searchParams.get('days')) params.set('days', searchParams.get('days')!); + const qs = params.toString() ? `?${params}` : ''; + const data = await nasGet(`/api/lotto/purchase${qs}`); + return NextResponse.json(data); + } catch (err) { return handleNasError(err); } } export async function POST(request: Request) { try { const auth = await requireSubscription(); if (auth instanceof NextResponse) return auth; - - const supabase = await createClient(); const body = await request.json(); - - const { data, error } = await supabase - .from('lotto_purchases') - .insert({ - user_id: auth.userId, - draw_no: body.draw_no, - amount: body.amount ?? 5000, - sets: body.sets ?? 5, - prize: body.prize ?? 0, - note: body.note ?? '', - }) - .select() - .single(); - - if (error) throw error; + const data = await nasPost('/api/lotto/purchase', body); return NextResponse.json(data, { status: 201 }); - } catch (err) { - console.error('[purchase POST]', err); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } + } catch (err) { return handleNasError(err); } } diff --git a/app/api/lotto/purchase/stats/route.ts b/app/api/lotto/purchase/stats/route.ts index 14dc019..4464c32 100644 --- a/app/api/lotto/purchase/stats/route.ts +++ b/app/api/lotto/purchase/stats/route.ts @@ -1,37 +1,13 @@ import { NextResponse } from 'next/server'; -import { createClient } from '@/lib/supabase/server'; -import { requireSubscription } from '../../_nas'; +import { nasGet, requireSubscription, handleNasError } from '../../_nas'; + +export const maxDuration = 60; export async function GET() { try { const auth = await requireSubscription(); if (auth instanceof NextResponse) return auth; - - const supabase = await createClient(); - const { data, error } = await supabase - .from('lotto_purchases') - .select('amount, prize') - .eq('user_id', auth.userId); - - if (error) throw error; - - const records = data ?? []; - const total_invested = records.reduce((s, r) => s + (r.amount ?? 0), 0); - const total_prize = records.reduce((s, r) => s + (r.prize ?? 0), 0); - const prize_count = records.filter(r => (r.prize ?? 0) > 0).length; - const max_prize = records.reduce((m, r) => Math.max(m, r.prize ?? 0), 0); - - return NextResponse.json({ - total_records: records.length, - total_invested, - total_prize, - net: total_prize - total_invested, - return_rate: total_invested > 0 ? Math.round((total_prize / total_invested) * 1000) / 10 : 0, - prize_count, - max_prize, - }); - } catch (err) { - console.error('[purchase/stats]', err); - return NextResponse.json({ error: 'DB_ERROR' }, { status: 500 }); - } + const data = await nasGet('/api/lotto/purchase/stats'); + return NextResponse.json(data); + } catch (err) { return handleNasError(err); } }