rui-weather-service-api
Version:
Weather API service with OpenWeatherMap integration - includes both standalone API and MCP server
180 lines • 6.65 kB
JavaScript
import { fetchWithRetry } from '../utils/http-client.js';
import config, { updateConfig, validateConfig } from '../config/index.js';
/**
* Weather API Client for OpenWeatherMap
*/
export class WeatherApiClient {
/**
* Creates a new WeatherApiClient instance
* @param options Configuration options
*/
constructor(options = {}) {
// Update configuration with provided options
if (Object.keys(options).length > 0) {
updateConfig(options);
}
}
/**
* Gets current weather for a location
* @param city City name
* @returns Weather data
*/
async getCurrentWeather(city) {
// Validate inputs
this.validateInputs(city);
console.log(`Fetching current weather for ${city}...`);
const params = {
q: city,
appid: config.apiKey,
units: config.units,
lang: config.language
};
// Fetch data with retry capability
const weatherData = await fetchWithRetry('/weather', params);
// Map API response to our structured format
return {
location: {
name: weatherData.name,
country: weatherData.sys.country,
coordinates: {
lat: weatherData.coord.lat,
lon: weatherData.coord.lon
}
},
temperature: {
current: weatherData.main.temp,
feelsLike: weatherData.main.feels_like,
min: weatherData.main.temp_min,
max: weatherData.main.temp_max
},
weather: {
main: weatherData.weather[0].main,
description: weatherData.weather[0].description,
icon: weatherData.weather[0].icon
},
wind: {
speed: weatherData.wind.speed,
direction: weatherData.wind.deg
},
details: {
humidity: weatherData.main.humidity,
pressure: weatherData.main.pressure,
visibility: weatherData.visibility / 1000,
sunrise: new Date(weatherData.sys.sunrise * 1000),
sunset: new Date(weatherData.sys.sunset * 1000)
},
updatedAt: new Date(weatherData.dt * 1000),
raw: weatherData
};
}
/**
* Gets weather forecast for a location
* @param city City name
* @param days Number of days (1-5)
* @returns Forecast data
*/
async getForecast(city, days = 5) {
// Validate inputs
this.validateInputs(city);
// Validate days parameter
if (isNaN(days) || days < 1 || days > 5) {
throw new Error('Days parameter must be between 1 and 5');
}
console.log(`Fetching ${days}-day forecast for ${city}...`);
const params = {
q: city,
appid: config.apiKey,
units: config.units,
lang: config.language,
cnt: Math.min(days * 8, 40), // Each day has 8 forecasts (every 3 hours), max 5 days
};
// Fetch data with retry capability
const forecastData = await fetchWithRetry('/forecast', params);
// Process forecast data by day
const dailyForecasts = {};
forecastData.list.forEach((item) => {
const date = new Date(item.dt * 1000).toLocaleDateString();
if (!dailyForecasts[date]) {
dailyForecasts[date] = {
date: new Date(item.dt * 1000),
temperature: {
min: item.main.temp,
max: item.main.temp,
avg: item.main.temp
},
weather: {
main: item.weather[0].main,
description: item.weather[0].description,
icon: item.weather[0].icon
},
wind: {
speed: item.wind.speed,
direction: item.wind.deg
},
details: {
humidity: item.main.humidity,
pressure: item.main.pressure
},
hourlyForecasts: []
};
}
else {
// Update min/max temperatures
dailyForecasts[date].temperature.min = Math.min(dailyForecasts[date].temperature.min, item.main.temp);
dailyForecasts[date].temperature.max = Math.max(dailyForecasts[date].temperature.max, item.main.temp);
}
// Add hourly forecast
dailyForecasts[date].hourlyForecasts.push({
time: new Date(item.dt * 1000),
temperature: item.main.temp,
feelsLike: item.main.feels_like,
weather: {
main: item.weather[0].main,
description: item.weather[0].description,
icon: item.weather[0].icon
},
wind: {
speed: item.wind.speed,
direction: item.wind.deg
},
details: {
humidity: item.main.humidity,
pressure: item.main.pressure
}
});
});
// Calculate average temperatures
Object.keys(dailyForecasts).forEach(date => {
const forecasts = dailyForecasts[date].hourlyForecasts;
const sum = forecasts.reduce((acc, curr) => acc + curr.temperature, 0);
dailyForecasts[date].temperature.avg = sum / forecasts.length;
});
return {
city: {
name: forecastData.city.name,
country: forecastData.city.country,
coordinates: {
lat: forecastData.city.coord.lat,
lon: forecastData.city.coord.lon
}
},
forecasts: Object.values(dailyForecasts),
raw: forecastData
};
}
/**
* Validates input parameters
* @param city City name
*/
validateInputs(city) {
// Validate city parameter
if (!city || city.trim() === '') {
throw new Error('City name is required');
}
// Validate API key
validateConfig();
}
}
// Export a default instance for convenience
export const weatherApi = new WeatherApiClient();
//# sourceMappingURL=weather-api.js.map