Skip to content

Commit ceefa03

Browse files
feat: refactor report page to use Open-Meteo API (#11)
The report page was successfully refactored to retrieve weather data directly from the Open-Meteo API via client-side JavaScript, removing the dependency on backend data. The ReportController now only passes city, latitude, and longitude to the frontend, with no server-side weather data fetching. All changes were compiled without errors. Co-authored-by: jetbrains-junie[bot] <201638009+jetbrains-junie[bot]@users.noreply.github.com>
1 parent c854a18 commit ceefa03

2 files changed

Lines changed: 96 additions & 74 deletions

File tree

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
package com.example.weatherapp.web;
22

3-
import com.example.weatherapp.weather.WeatherService;
4-
import com.example.weatherapp.weather.WeatherService.WeatherReport;
5-
import com.fasterxml.jackson.core.JsonProcessingException;
6-
import com.fasterxml.jackson.databind.ObjectMapper;
73
import org.springframework.stereotype.Controller;
84
import org.springframework.ui.Model;
95
import org.springframework.web.bind.annotation.GetMapping;
@@ -12,40 +8,24 @@
128
@Controller
139
public class ReportController {
1410

15-
private final WeatherService weatherService;
16-
private final ObjectMapper objectMapper;
17-
18-
public ReportController(WeatherService weatherService) {
19-
this.weatherService = weatherService;
20-
this.objectMapper = new ObjectMapper();
11+
public ReportController() {
2112
}
2213

2314
@GetMapping("/report")
2415
public String report(@RequestParam("lat") double lat,
2516
@RequestParam("lon") double lon,
2617
@RequestParam(value = "city", required = false) String city,
27-
Model model) throws JsonProcessingException {
18+
Model model) {
2819
// Basic validation of ranges
2920
if (lat < -90 || lat > 90 || lon < -180 || lon > 180) {
3021
model.addAttribute("error", "Invalid coordinates.");
3122
return "report";
3223
}
3324

34-
try {
35-
WeatherReport report = weatherService.fetchHourlyReport(lat, lon);
36-
String reportJson = objectMapper.writeValueAsString(report);
37-
38-
model.addAttribute("city", city);
39-
model.addAttribute("lat", lat);
40-
model.addAttribute("lon", lon);
41-
model.addAttribute("timezone", report.getTimezone());
42-
model.addAttribute("reportJson", reportJson);
43-
} catch (Exception ex) {
44-
model.addAttribute("city", city);
45-
model.addAttribute("lat", lat);
46-
model.addAttribute("lon", lon);
47-
model.addAttribute("error", "Failed to fetch weather data. Please try again later.");
48-
}
25+
// Pass only basic info. Weather data will be fetched on the client side.
26+
model.addAttribute("city", city);
27+
model.addAttribute("lat", lat);
28+
model.addAttribute("lon", lon);
4929
return "report";
5030
}
5131
}

src/main/resources/templates/report.html

Lines changed: 90 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -93,76 +93,118 @@
9393
<h1>
9494
Weather report
9595
<span th:if="${city}">for <span th:text="${city}"></span></span>
96-
(<span th:text="${lat}">lat</span>, <span th:text="${lon}">lon</span>)
96+
(<span id="lat-value" th:text="${lat}">lat</span>, <span id="lon-value" th:text="${lon}">lon</span>)
9797
</h1>
98-
<p class="muted">Timezone: <span th:text="${timezone}">auto</span></p>
98+
<p class="muted">Timezone: <span id="timezone">auto</span></p>
9999

100-
<div th:if="${error}" class="error" th:text="${error}">An error occurred</div>
100+
<div th:if="${error}" id="server-error" class="error" th:text="${error}">An error occurred</div>
101+
<div id="client-error" class="error" style="display:none"></div>
101102

102-
<div id="charts" class="charts" th:if="${reportJson}">
103+
<div id="loading" class="muted">Loading weather data…</div>
104+
105+
<div id="charts" class="charts" style="display:none">
103106
<div id="tempChart" class="chart"></div>
104107
<div id="precipChart" class="chart"></div>
105108
<div id="windChart" class="chart"></div>
106109
<div id="humidityChart" class="chart"></div>
107110
</div>
108111

109-
<script id="report-json" type="application/json" th:utext="${reportJson}">{}</script>
110-
111112
<script>
112113
// Load Google Charts
113114
google.charts.load('current', { packages: ['corechart'] });
114-
google.charts.setOnLoadCallback(drawCharts);
115+
google.charts.setOnLoadCallback(initReport);
115116

116-
// Store chart data/options for redraw
117117
let chartDataCache = {};
118118

119-
function drawCharts() {
120-
const jsonEl = document.getElementById('report-json');
121-
if (!jsonEl) return;
122-
let report;
123-
try {
124-
report = JSON.parse(jsonEl.textContent || '{}');
125-
} catch (e) {
126-
console.error('Failed to parse report JSON', e);
119+
function initReport() {
120+
// If server-side validation failed, don't attempt client fetch
121+
if (document.getElementById('server-error')) {
122+
document.getElementById('loading').style.display = 'none';
127123
return;
128124
}
129-
if (!report || !report.times || !report.temperature_2m) {
130-
console.warn('No report data');
125+
const latText = (document.getElementById('lat-value')?.textContent || '').trim();
126+
const lonText = (document.getElementById('lon-value')?.textContent || '').trim();
127+
const lat = parseFloat(latText);
128+
const lon = parseFloat(lonText);
129+
if (!Number.isFinite(lat) || !Number.isFinite(lon)) {
130+
showClientError('Invalid coordinates.');
131131
return;
132132
}
133+
fetchAndDraw(lat, lon);
134+
}
135+
136+
async function fetchAndDraw(lat, lon) {
137+
const url = new URL('https://api.open-meteo.com/v1/forecast');
138+
url.searchParams.set('latitude', lat);
139+
url.searchParams.set('longitude', lon);
140+
url.searchParams.set('hourly', [
141+
'temperature_2m',
142+
'precipitation',
143+
'wind_speed_10m',
144+
'relative_humidity_2m'
145+
].join(','));
146+
url.searchParams.set('forecast_days', '3');
147+
url.searchParams.set('timezone', 'auto');
148+
149+
try {
150+
const resp = await fetch(url.toString());
151+
if (!resp.ok) throw new Error('Weather API request failed');
152+
const data = await resp.json();
153+
const hourly = data.hourly || {};
154+
// Update timezone display
155+
const tz = data.timezone || 'auto';
156+
const tzEl = document.getElementById('timezone');
157+
if (tzEl) tzEl.textContent = tz;
133158

134-
// Helper to convert ISO datetime string to JS Date
135-
const toDate = (s) => new Date(String(s).replace(' ', 'T'));
136-
137-
// Prepare data for charts and cache for redraw
138-
chartDataCache = {
139-
tempChart: {
140-
type: 'LineChart',
141-
title: 'Temperature 2m (°C)',
142-
columns: ['Time', 'Temperature (°C)'],
143-
rows: report.times.map((t, i) => [toDate(t), safeNumber(report.temperature_2m[i])])
144-
},
145-
precipChart: {
146-
type: 'ColumnChart',
147-
title: 'Precipitation (mm)',
148-
columns: ['Time', 'Precipitation (mm)'],
149-
rows: report.times.map((t, i) => [toDate(t), safeNumber(report.precipitation?.[i])])
150-
},
151-
windChart: {
152-
type: 'LineChart',
153-
title: 'Wind speed 10m (m/s)',
154-
columns: ['Time', 'Wind speed (m/s)'],
155-
rows: report.times.map((t, i) => [toDate(t), safeNumber(report.wind_speed_10m?.[i])])
156-
},
157-
humidityChart: {
158-
type: 'LineChart',
159-
title: 'Relative humidity 2m (%)',
160-
columns: ['Time', 'Humidity (%)'],
161-
rows: report.times.map((t, i) => [toDate(t), safeNumber(report.relative_humidity_2m?.[i])])
159+
if (!hourly.time || !hourly.temperature_2m) {
160+
throw new Error('Incomplete data from weather API');
162161
}
163-
};
164162

165-
redrawAllCharts();
163+
// Helper to convert ISO datetime string to JS Date
164+
const toDate = (s) => new Date(String(s));
165+
166+
// Prepare data for charts and cache for redraw
167+
chartDataCache = {
168+
tempChart: {
169+
type: 'LineChart',
170+
title: 'Temperature 2m (°C)',
171+
columns: ['Time', 'Temperature (°C)'],
172+
rows: hourly.time.map((t, i) => [toDate(t), safeNumber(hourly.temperature_2m[i])])
173+
},
174+
precipChart: {
175+
type: 'ColumnChart',
176+
title: 'Precipitation (mm)',
177+
columns: ['Time', 'Precipitation (mm)'],
178+
rows: hourly.time.map((t, i) => [toDate(t), safeNumber(hourly.precipitation?.[i])])
179+
},
180+
windChart: {
181+
type: 'LineChart',
182+
title: 'Wind speed 10m (m/s)',
183+
columns: ['Time', 'Wind speed (m/s)'],
184+
rows: hourly.time.map((t, i) => [toDate(t), safeNumber(hourly.wind_speed_10m?.[i])])
185+
},
186+
humidityChart: {
187+
type: 'LineChart',
188+
title: 'Relative humidity 2m (%)',
189+
columns: ['Time', 'Humidity (%)'],
190+
rows: hourly.time.map((t, i) => [toDate(t), safeNumber(hourly.relative_humidity_2m?.[i])])
191+
}
192+
};
193+
194+
document.getElementById('loading').style.display = 'none';
195+
document.getElementById('charts').style.display = '';
196+
redrawAllCharts();
197+
} catch (e) {
198+
console.error(e);
199+
showClientError('Failed to fetch weather data. Please try again later.');
200+
}
201+
}
202+
203+
function showClientError(message) {
204+
document.getElementById('loading').style.display = 'none';
205+
const el = document.getElementById('client-error');
206+
el.textContent = message || 'An error occurred';
207+
el.style.display = '';
166208
}
167209

168210
function safeNumber(v) {

0 commit comments

Comments
 (0)