| import { NextRequest, NextResponse } from 'next/server' |
|
|
| import { apiUrl, API_BASE } from '@/lib/runtime-config' |
| import { requireAuth } from '@/lib/api-auth' |
| import { getStockQuote } from '@/lib/yahoo-finance' |
|
|
| export const dynamic = 'force-dynamic' |
|
|
| const US_MOVERS_POOL = [ |
| 'AAPL','MSFT','GOOGL','AMZN','NVDA','META','TSLA','BRK-B','JPM', |
| 'V','JNJ','WMT','PG','MA','UNH','HD','DIS','BAC','XOM','PFE', |
| 'NFLX','INTC','AMD','CRM','PYPL','ADBE','COST','CSCO','CMCSA','ABT', |
| ] |
|
|
| export async function GET(_req: NextRequest) { |
| const auth = await requireAuth(_req) |
| if (!auth.authenticated) return auth.response |
|
|
| const { searchParams } = new URL(_req.url) |
| const market = (searchParams.get('market') || 'bist') as 'bist' | 'us' |
|
|
| if (market === 'us') { |
| try { |
| const results = await Promise.allSettled( |
| US_MOVERS_POOL.map(async (sym) => { |
| const q = await getStockQuote(sym, 'us') |
| return { symbol: sym, last_price: q.price, change_pct: q.changePercent, volume: q.volume } |
| }) |
| ) |
| const stocks = results |
| .filter((r): r is PromiseFulfilledResult<{ symbol: string; last_price: number; change_pct: number; volume: number }> => r.status === 'fulfilled') |
| .map((r) => r.value) |
| const sorted = [...stocks].sort((a, b) => b.change_pct - a.change_pct) |
| const gainers = sorted.filter((s) => s.change_pct > 0).slice(0, 10) |
| const losers = sorted.filter((s) => s.change_pct < 0).sort((a, b) => a.change_pct - b.change_pct).slice(0, 10) |
| return NextResponse.json({ ok: true, gainers, losers }, { status: 200 }) |
| } catch (e: unknown) { |
| const message = e instanceof Error ? e.message : 'Unknown' |
| return NextResponse.json({ ok: false, error: `US movers failed: ${message}`, gainers: [], losers: [] }, { status: 502 }) |
| } |
| } |
|
|
| try { |
| const controller = new AbortController() |
| const timeout = setTimeout(() => controller.abort(), 30000) |
|
|
| let resp: Response |
| try { |
| resp = await fetch(apiUrl('/api/movers'), { |
| headers: { accept: 'application/json' }, |
| next: { revalidate: 300 }, |
| signal: controller.signal, |
| }) |
| } finally { |
| clearTimeout(timeout) |
| } |
|
|
| if (!resp.ok) { |
| return NextResponse.json( |
| { ok: false, error: `Movers backend failed (HTTP ${resp.status})`, gainers: [], losers: [] }, |
| { status: 502 } |
| ) |
| } |
|
|
| const payload = await resp.json().catch(() => null) |
| if (!payload) { |
| return NextResponse.json( |
| { ok: false, error: 'Invalid payload from movers backend', gainers: [], losers: [] }, |
| { status: 502 } |
| ) |
| } |
|
|
| const payloadObj = payload as Record<string, unknown> |
| const gainers = Array.isArray(payloadObj?.gainers) ? payloadObj.gainers as unknown[] : [] |
| const losers = Array.isArray(payloadObj?.losers) ? payloadObj.losers as unknown[] : [] |
|
|
| return NextResponse.json({ ok: true, gainers, losers }, { status: 200 }) |
| } catch (e: unknown) { |
| const message = e instanceof Error ? e.message : 'Unknown' |
| return NextResponse.json( |
| { ok: false, error: `Movers fetch failed: ${message}`, gainers: [], losers: [] }, |
| { status: 502 } |
| ) |
| } |
| } |
|
|