feat: geo-device-service, Weather по геопозиции, Discover вкладка GooSeek

Geo Device Service:
- Новый сервис определения геопозиции, устройства, браузера
- geoip-lite, ua-parser-js, CORS
- GET/POST /api/context

Frontend:
- /api/geo-context — прокси к geo-device, fallback при недоступности
- geoDevice.ts — fetchContextWithClient, fetchContextWithGeolocation
- Weather: геопозиция через geo-device + GeoJS + ipwhois fallback
- Weather API: поддержка city (геокодинг Open-Meteo)
- Discover: вкладка GooSeek по умолчанию

Документация:
- MICROSERVICES.md — секция 3.9 Geo Device Service

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
home
2026-02-20 17:20:14 +03:00
parent 783569b8e7
commit 8ba3f5495a
18 changed files with 958 additions and 96 deletions

View File

@@ -1,22 +1,50 @@
const geocodeCity = async (city: string): Promise<{ lat: number; lng: number; name: string } | null> => {
const res = await fetch(
`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1`,
);
const data = await res.json();
const result = data.results?.[0];
if (!result?.latitude || !result?.longitude) return null;
return {
lat: result.latitude,
lng: result.longitude,
name: result.name ?? city,
};
};
export const POST = async (req: Request) => {
try {
const body: {
lat: number;
lng: number;
lat?: number;
lng?: number;
city?: string;
measureUnit: 'Imperial' | 'Metric';
} = await req.json();
if (!body.lat || !body.lng) {
let lat: number;
let lng: number;
let cityName: string | undefined = body.city;
if (body.lat != null && body.lng != null) {
lat = body.lat;
lng = body.lng;
} else if (body.city?.trim()) {
const geo = await geocodeCity(body.city.trim());
if (!geo) {
return Response.json({ message: 'City not found.' }, { status: 404 });
}
lat = geo.lat;
lng = geo.lng;
cityName = geo.name;
} else {
return Response.json(
{
message: 'Invalid request.',
},
{ message: 'Invalid request. Provide lat/lng or city.' },
{ status: 400 },
);
}
const res = await fetch(
`https://api.open-meteo.com/v1/forecast?latitude=${body.lat}&longitude=${body.lng}&current=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${
`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lng}&current=weather_code,temperature_2m,is_day,relative_humidity_2m,wind_speed_10m&timezone=auto${
body.measureUnit === 'Metric' ? '' : '&temperature_unit=fahrenheit'
}${body.measureUnit === 'Metric' ? '' : '&wind_speed_unit=mph'}`,
);
@@ -159,7 +187,7 @@ export const POST = async (req: Request) => {
break;
}
return Response.json(weather);
return Response.json({ ...weather, city: cityName });
} catch (err) {
console.error('An error occurred while getting home widgets', err);
return Response.json(