Files
paragliding/frontend/lib/api.ts
2026-01-03 14:16:16 -08:00

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)