feat(weather): migrate /api/weather to the NestJS pilot module (L1) (#1053)

First strangler migration (L1): /api/weather is served by a NestJS module.

- @trek/shared/weather Zod contract; Nest controller byte-identical to the legacy Express route (paths, query params, status codes, { error } bodies, lang default, ApiError/500 passthrough). Service reuses getWeather/getDetailedWeather (+ shared cache; MCP tools unchanged).
- Strangler routes /api/weather to Nest by default; the legacy Express route + its migration-time parity test were decommissioned in this PR.
- Frontend (FE2): weatherApi typed against the @trek/shared WeatherResult contract.
- Harness: reusable Nest-vs-Express parity harness, e2e harness (temp SQLite + seed/cookie helpers, real JwtAuthGuard), src/nest coverage gate raised to >=80%, src/nest test guide.
- Verified end-to-end on a prod mirror (dev1): 401/400/200 via Nest with real Open-Meteo data, Express route gone.
This commit is contained in:
Maurice
2026-05-25 17:00:58 +02:00
committed by GitHub
parent 0b218d53b2
commit 0257e4e71e
21 changed files with 614 additions and 317 deletions
-45
View File
@@ -1,45 +0,0 @@
import express, { Request, Response } from 'express';
import { authenticate } from '../middleware/auth';
import { getWeather, getDetailedWeather, ApiError } from '../services/weatherService';
const router = express.Router();
router.get('/', authenticate, async (req: Request, res: Response) => {
const { lat, lng, date, lang = 'de' } = req.query as { lat: string; lng: string; date?: string; lang?: string };
if (!lat || !lng) {
return res.status(400).json({ error: 'Latitude and longitude are required' });
}
try {
const result = await getWeather(lat, lng, date, lang);
res.json(result);
} catch (err: unknown) {
if (err instanceof ApiError) {
return res.status(err.status).json({ error: err.message });
}
console.error('Weather error:', err);
res.status(500).json({ error: 'Error fetching weather data' });
}
});
router.get('/detailed', authenticate, async (req: Request, res: Response) => {
const { lat, lng, date, lang = 'de' } = req.query as { lat: string; lng: string; date: string; lang?: string };
if (!lat || !lng || !date) {
return res.status(400).json({ error: 'Latitude, longitude, and date are required' });
}
try {
const result = await getDetailedWeather(lat, lng, date, lang);
res.json(result);
} catch (err: unknown) {
if (err instanceof ApiError) {
return res.status(err.status).json({ error: err.message });
}
console.error('Detailed weather error:', err);
res.status(500).json({ error: 'Error fetching detailed weather data' });
}
});
export default router;