- Add weather feature with Open-Meteo API: hourly temp/icon overlay on time slots, location search via geocoding, weather toggle in settings - Move project management to dedicated settings tab with emoji icon picker - Fix iPhone single-day view starting on yesterday instead of today - Fix deleted Apple calendar events persisting until app reboot by scheduling a delayed re-fetch after stale background sync v1.44.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
68 lines
2.7 KiB
TypeScript
68 lines
2.7 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server';
|
|
import { getServerSession } from 'next-auth';
|
|
import { authOptions } from '@/lib/auth';
|
|
import { prisma } from '@/lib/prisma';
|
|
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
// In-memory cache: { key: { data, fetchedAt } }
|
|
const weatherCache = new Map<string, { data: any; fetchedAt: number }>();
|
|
const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
|
|
|
|
export async function GET(request: NextRequest) {
|
|
const session = await getServerSession(authOptions);
|
|
const userId = (session?.user as any)?.id;
|
|
if (!userId) {
|
|
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
}
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId },
|
|
select: { weatherEnabled: true, weatherLat: true, weatherLon: true },
|
|
});
|
|
|
|
if (!user?.weatherEnabled || !user.weatherLat || !user.weatherLon) {
|
|
return NextResponse.json({ error: 'Weather not configured' }, { status: 400 });
|
|
}
|
|
|
|
const { searchParams } = new URL(request.url);
|
|
const startDate = searchParams.get('start') || new Date().toISOString().slice(0, 10);
|
|
const endDate = searchParams.get('end') || startDate;
|
|
|
|
const cacheKey = `${user.weatherLat},${user.weatherLon},${startDate},${endDate}`;
|
|
const cached = weatherCache.get(cacheKey);
|
|
if (cached && Date.now() - cached.fetchedAt < CACHE_TTL_MS) {
|
|
return NextResponse.json(cached.data);
|
|
}
|
|
|
|
try {
|
|
const url = `https://api.open-meteo.com/v1/forecast?latitude=${user.weatherLat}&longitude=${user.weatherLon}&hourly=temperature_2m,weather_code&start_date=${startDate}&end_date=${endDate}&timezone=auto`;
|
|
const res = await fetch(url, { next: { revalidate: 900 } });
|
|
|
|
if (!res.ok) {
|
|
return NextResponse.json({ error: 'Weather API failed' }, { status: 502 });
|
|
}
|
|
|
|
const raw = await res.json();
|
|
|
|
// Transform into { "2026-03-17T08:00": { temp: 5, code: 2 }, ... }
|
|
const hourly: Record<string, { temp: number; code: number }> = {};
|
|
if (raw.hourly?.time && raw.hourly?.temperature_2m && raw.hourly?.weather_code) {
|
|
for (let i = 0; i < raw.hourly.time.length; i++) {
|
|
hourly[raw.hourly.time[i]] = {
|
|
temp: Math.round(raw.hourly.temperature_2m[i]),
|
|
code: raw.hourly.weather_code[i],
|
|
};
|
|
}
|
|
}
|
|
|
|
const data = { hourly, timezone: raw.timezone };
|
|
weatherCache.set(cacheKey, { data, fetchedAt: Date.now() });
|
|
|
|
return NextResponse.json(data);
|
|
} catch (err) {
|
|
console.error('[WEATHER] Fetch failed:', err);
|
|
return NextResponse.json({ error: 'Weather fetch failed' }, { status: 500 });
|
|
}
|
|
}
|