208 lines
7.0 KiB
TypeScript
208 lines
7.0 KiB
TypeScript
import {
|
|
CurrentWeatherResponse,
|
|
ForecastResponse,
|
|
HistoricalResponse,
|
|
AssessmentResponse,
|
|
Thresholds,
|
|
APIError,
|
|
} from './types'
|
|
|
|
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080/api/v1'
|
|
|
|
class APIClient {
|
|
private baseURL: string
|
|
|
|
constructor(baseURL: string) {
|
|
this.baseURL = baseURL
|
|
}
|
|
|
|
private async request<T>(
|
|
endpoint: string,
|
|
options?: RequestInit
|
|
): Promise<T> {
|
|
const url = `${this.baseURL}${endpoint}`
|
|
|
|
try {
|
|
const response = await fetch(url, {
|
|
...options,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...options?.headers,
|
|
},
|
|
})
|
|
|
|
if (!response.ok) {
|
|
const error: APIError = await response.json().catch(() => ({
|
|
error: 'An error occurred',
|
|
detail: response.statusText,
|
|
}))
|
|
throw new Error(error.error || `HTTP ${response.status}: ${response.statusText}`)
|
|
}
|
|
|
|
const json = await response.json()
|
|
// Unwrap {success, data} response if present
|
|
return (json.data || json) as T
|
|
} catch (error) {
|
|
if (error instanceof Error) {
|
|
throw error
|
|
}
|
|
throw new Error('An unexpected error occurred')
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get current weather conditions and flyability assessment
|
|
*/
|
|
async getCurrentWeather(
|
|
lat?: number,
|
|
lon?: number
|
|
): Promise<CurrentWeatherResponse> {
|
|
const params = new URLSearchParams()
|
|
if (lat !== undefined) params.append('lat', lat.toString())
|
|
if (lon !== undefined) params.append('lon', lon.toString())
|
|
|
|
const query = params.toString() ? `?${params.toString()}` : ''
|
|
const data = await this.request<any>(`/weather/current${query}`)
|
|
|
|
// Transform backend response to frontend format
|
|
return {
|
|
location: data.location || { lat: 0, lon: 0 },
|
|
current: {
|
|
timestamp: data.current?.time || data.current?.timestamp,
|
|
wind_speed: data.current?.windSpeed || data.current?.wind_speed || 0,
|
|
wind_direction: data.current?.windDirection || data.current?.wind_direction || 0,
|
|
wind_gust: data.current?.windGust || data.current?.wind_gust || 0,
|
|
temperature: data.current?.temperature || 0,
|
|
cloud_cover: data.current?.cloudCover || data.current?.cloud_cover || 0,
|
|
precipitation: data.current?.precipitation || 0,
|
|
visibility: data.current?.visibility || 0,
|
|
pressure: data.current?.pressure || 0,
|
|
humidity: data.current?.humidity || 0,
|
|
},
|
|
assessment: {
|
|
is_flyable: data.assessment?.FlyableNow || data.assessment?.is_flyable || false,
|
|
reasons: data.assessment?.Reason ? [data.assessment.Reason] : data.assessment?.reasons || [],
|
|
score: data.assessment?.score || 0,
|
|
},
|
|
last_updated: data.timestamp || data.last_updated || new Date().toISOString(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get weather forecast for the next 7 days
|
|
*/
|
|
async getForecast(
|
|
lat?: number,
|
|
lon?: number
|
|
): Promise<ForecastResponse> {
|
|
const params = new URLSearchParams()
|
|
if (lat !== undefined) params.append('lat', lat.toString())
|
|
if (lon !== undefined) params.append('lon', lon.toString())
|
|
|
|
const query = params.toString() ? `?${params.toString()}` : ''
|
|
const data = await this.request<any>(`/weather/forecast${query}`)
|
|
|
|
// Transform backend response to frontend format
|
|
return {
|
|
location: data.location || { lat: 0, lon: 0 },
|
|
forecast: (data.forecast || []).map((point: any) => ({
|
|
timestamp: point.Time || point.timestamp,
|
|
wind_speed: point.WindSpeedMPH || point.wind_speed || 0,
|
|
wind_direction: point.WindDirection || point.wind_direction || 0,
|
|
wind_gust: point.WindGustMPH || point.wind_gust || 0,
|
|
temperature: point.temperature || 0,
|
|
cloud_cover: point.cloud_cover || 0,
|
|
precipitation: point.precipitation || 0,
|
|
visibility: point.visibility || 0,
|
|
pressure: point.pressure || 0,
|
|
humidity: point.humidity || 0,
|
|
})),
|
|
flyable_windows: (data.flyableWindows || data.flyable_windows || []).map((win: any) => ({
|
|
start: win.start,
|
|
end: win.end,
|
|
duration_hours: win.durationHours || win.duration_hours || 0,
|
|
avg_conditions: {
|
|
wind_speed: win.avgConditions?.windSpeed || win.avg_conditions?.wind_speed || 0,
|
|
wind_gust: win.avgConditions?.windGust || win.avg_conditions?.wind_gust || 0,
|
|
temperature: win.avgConditions?.temperature || win.avg_conditions?.temperature || 0,
|
|
cloud_cover: win.avgConditions?.cloudCover || win.avg_conditions?.cloud_cover || 0,
|
|
},
|
|
})),
|
|
generated_at: data.generated || data.generated_at || new Date().toISOString(),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get historical weather data for a specific date
|
|
* @param date - Date in YYYY-MM-DD format
|
|
*/
|
|
async getHistorical(
|
|
date: string,
|
|
lat?: number,
|
|
lon?: number
|
|
): Promise<HistoricalResponse> {
|
|
const params = new URLSearchParams({ date })
|
|
if (lat !== undefined) params.append('lat', lat.toString())
|
|
if (lon !== undefined) params.append('lon', lon.toString())
|
|
|
|
const data = await this.request<any>(`/weather/historical?${params.toString()}`)
|
|
|
|
// Transform backend response to frontend format
|
|
return {
|
|
location: data.location || { lat: 0, lon: 0 },
|
|
date: data.date || date,
|
|
data: (data.data || []).map((point: any) => ({
|
|
timestamp: point.Time || point.timestamp,
|
|
wind_speed: point.WindSpeedMPH || point.wind_speed || 0,
|
|
wind_direction: point.WindDirection || point.wind_direction || 0,
|
|
wind_gust: point.WindGustMPH || point.wind_gust || 0,
|
|
temperature: point.temperature || 0,
|
|
cloud_cover: point.cloud_cover || 0,
|
|
precipitation: point.precipitation || 0,
|
|
visibility: point.visibility || 0,
|
|
pressure: point.pressure || 0,
|
|
humidity: point.humidity || 0,
|
|
})),
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Assess current conditions with custom thresholds
|
|
*/
|
|
async assessWithThresholds(
|
|
thresholds: Thresholds,
|
|
lat?: number,
|
|
lon?: number
|
|
): Promise<AssessmentResponse> {
|
|
const params = new URLSearchParams()
|
|
if (lat !== undefined) params.append('lat', lat.toString())
|
|
if (lon !== undefined) params.append('lon', lon.toString())
|
|
|
|
const query = params.toString() ? `?${params.toString()}` : ''
|
|
|
|
return this.request<AssessmentResponse>(`/weather/assess${query}`, {
|
|
method: 'POST',
|
|
body: JSON.stringify({ thresholds }),
|
|
})
|
|
}
|
|
}
|
|
|
|
// Export singleton instance
|
|
export const apiClient = new APIClient(API_BASE_URL)
|
|
|
|
// Export individual functions for convenience
|
|
export const getCurrentWeather = (lat?: number, lon?: number) =>
|
|
apiClient.getCurrentWeather(lat, lon)
|
|
|
|
export const getForecast = (lat?: number, lon?: number) =>
|
|
apiClient.getForecast(lat, lon)
|
|
|
|
export const getHistorical = (date: string, lat?: number, lon?: number) =>
|
|
apiClient.getHistorical(date, lat, lon)
|
|
|
|
export const assessWithThresholds = (
|
|
thresholds: Thresholds,
|
|
lat?: number,
|
|
lon?: number
|
|
) => apiClient.assessWithThresholds(thresholds, lat, lon)
|