import { NextResponse } from 'next/server'; import { db } from '@/db'; import { exchangeRatesHistory } from '@/db/schema'; import { ProxyAgent, setGlobalDispatcher } from 'undici'; export const dynamic = 'force-dynamic'; export const fetchCache = 'force-no-store'; export const runtime = 'nodejs'; const CURRENCIES = [ { from: 'USD', to: 'CNY' }, { from: 'HKD', to: 'CNY' }, ]; const JISU_API_BASE = 'https://api.jisuapi.com/exchange/convert'; interface JisuResult { from: string; to: string; rate: string; updatetime: string; } interface JisuResponse { status: number; msg: string; result: JisuResult; } async function fetchRate( from: string, to: string, apikey: string ): Promise<{ from: string; to: string; rate: string; success: true } | { from: string; success: false; error: string }> { const url = `${JISU_API_BASE}?appkey=${apikey}&from=${from}&to=${to}&amount=1`; try { const res = await fetch(url, { cache: 'no-store' }); if (!res.ok) { return { from, success: false, error: `HTTP ${res.status}` }; } const data: JisuResponse = await res.json(); if (data.status !== 0) { return { from, success: false, error: data.msg || `API status: ${data.status}` }; } if (!data.result || !data.result.rate) { return { from, success: false, error: 'Missing result.rate in response' }; } return { from, to: data.result.to, rate: data.result.rate, success: true, }; } catch (err) { console.error(`[ExchangeRate] Fetch ${from}/${to} failed:`, err); return { from, success: false, error: (err as Error).message }; } } export async function GET(req: Request) { const cronSecret = process.env.CRON_SECRET; const authHeader = req.headers.get('Authorization'); if (!cronSecret) { return NextResponse.json( { error: 'CRON_SECRET not configured' }, { status: 500 } ); } if (authHeader !== `Bearer ${cronSecret}`) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } const jisuApiKey = process.env.JISU_API_KEY; if (!jisuApiKey) { return NextResponse.json( { error: 'JISU_API_KEY not configured' }, { status: 500 } ); } const proxyUrl = process.env.HTTPS_PROXY; if (proxyUrl) { const proxyAgent = new ProxyAgent(proxyUrl); setGlobalDispatcher(proxyAgent); } const fetchPromises = CURRENCIES.map(({ from, to }) => fetchRate(from, to, jisuApiKey) ); const results = await Promise.allSettled(fetchPromises); const inserted: Array<{ from: string; to: string; rate: string }> = []; const failed: Array<{ from: string; error: string }> = []; for (let i = 0; i < results.length; i++) { const result = results[i]; const pair = CURRENCIES[i]; if (result.status === 'fulfilled') { const res = result.value; if (res.success) { try { await db.insert(exchangeRatesHistory).values({ fromCurrency: res.from, toCurrency: res.to, rate: res.rate, fetchTime: new Date(), }); inserted.push({ from: res.from, to: res.to, rate: res.rate }); console.log(`[ExchangeRate] ${res.from}/${res.to} = ${res.rate} -> saved`); } catch (dbErr) { failed.push({ from: res.from, error: `DB insert failed: ${(dbErr as Error).message}` }); console.error(`[ExchangeRate] DB insert failed for ${res.from}:`, dbErr); } } else { const failRes = res as { from: string; success: false; error: string }; failed.push({ from: failRes.from, error: failRes.error }); console.error(`[ExchangeRate] API error for ${failRes.from}: ${failRes.error}`); } } else { failed.push({ from: pair.from, error: result.reason?.message || 'Unknown error' }); console.error(`[ExchangeRate] Promise rejected for ${pair.from}:`, result.reason); } } return NextResponse.json({ success: true, timestamp: new Date().toISOString(), inserted: inserted.length, failed: failed.length, details: { inserted, failed, }, }); }