export const dynamic = 'force-dynamic' export const runtime = 'nodejs' import { NextResponse } from 'next/server' import { isBearerAuthorized, unauthorized } from '@/lib/voice-tools' const CITIES: Record = { spb: { name: 'Санкт-Петербург', lat: '59.9343', lon: '30.3351' }, msk: { name: 'Москва', lat: '55.7558', lon: '37.6173' }, nsk: { name: 'Новосибирск', lat: '55.0084', lon: '82.9357' }, ekb: { name: 'Екатеринбург', lat: '56.8389', lon: '60.6057' }, kzn: { name: 'Казань', lat: '55.7887', lon: '49.1221' }, sochi: { name: 'Сочи', lat: '43.5855', lon: '39.7231' }, krd: { name: 'Краснодар', lat: '45.0355', lon: '38.9753' }, } function resolveCity(raw: string | null): { lat: string; lon: string; name: string } { if (!raw) return CITIES.spb const q = raw.toLowerCase().trim() for (const c of Object.values(CITIES)) { if (c.name.toLowerCase().includes(q) || q.includes(c.name.toLowerCase())) return c } // common shorthands if (q.includes('питер') || q.includes('спб') || q.includes('петерб')) return CITIES.spb if (q.includes('москв') || q.includes('мск')) return CITIES.msk if (q.includes('сочи')) return CITIES.sochi if (q.includes('казан')) return CITIES.kzn return CITIES.spb } export async function GET(req: Request) { if (!isBearerAuthorized(req)) return unauthorized() const { searchParams } = new URL(req.url) const city = resolveCity(searchParams.get('city')) // Call our own /api/weather internally — its code already knows Open-Meteo. // Use loopback so we don't need auth forwarding; weather route doesn't require auth actually // (it just reads query and calls upstream). const baseUrl = `http://localhost:${process.env.PORT || '3000'}` const r = await fetch(`${baseUrl}/api/weather?lat=${city.lat}&lon=${city.lon}`, { cache: 'no-store', headers: { cookie: '' }, // bypass middleware (we're public internally) }).catch(() => null) // Fallback: hit Open-Meteo directly if our own endpoint didn't respond if (!r || !r.ok) { const meteo = await fetch( `https://api.open-meteo.com/v1/forecast?latitude=${city.lat}&longitude=${city.lon}` + `¤t=temperature_2m,apparent_temperature,relative_humidity_2m,wind_speed_10m,weather_code` + `&timezone=auto`, { cache: 'no-store' } ) if (!meteo.ok) { return NextResponse.json({ error: 'weather_unavailable' }, { status: 502 }) } const j = await meteo.json() const cur = j?.current || {} return NextResponse.json({ city: city.name, temp: Math.round(cur.temperature_2m ?? 0), feels_like: Math.round(cur.apparent_temperature ?? 0), humidity: Math.round(cur.relative_humidity_2m ?? 0), wind_mps: Math.round(((cur.wind_speed_10m ?? 0) / 3.6) * 10) / 10, weather_code: cur.weather_code, }) } const d = await r.json() return NextResponse.json({ city: city.name, temp: d.temp, feels_like: d.feelsLike, humidity: d.humidity, wind: d.windSpeed, desc: d.desc, forecast: (d.forecast || []).slice(0, 5).map((day: any) => ({ date: day.date, max: day.maxTemp, min: day.minTemp, desc: day.desc, })), }) }