FEAT: Added ability to query weatherservice API
Some checks failed
Build and Push Development Docker Image / build-and-push (push) Successful in 1m38s
Run Unit and Integration Tests / test (push) Has been cancelled

This commit is contained in:
2025-06-18 21:46:32 +01:00
parent 215f4a0e2a
commit 155f91c240
5 changed files with 122 additions and 10 deletions

View File

@ -0,0 +1,42 @@
export type MetarCloud = {
cover: string;
base: number | null;
};
export type Metar = {
metar_id: number;
icaoId: string;
receiptTime: string;
obsTime: number;
reportTime: string;
temp: number;
dewp: number;
wdir: number;
wspd: number;
wgst: number | null;
visib: string;
altim: number;
slp: number | null;
qcField: number;
wxString: string | null;
presTend: string | null;
maxT: number | null;
minT: number | null;
maxT24: number | null;
minT24: number | null;
precip: number | null;
pcp3hr: number | null;
pcp6hr: number | null;
pcp24hr: number | null;
snow: number | null;
vertVis: number | null;
metarType: string;
rawOb: string;
mostRecent: number;
lat: number;
lon: number;
elev: number;
prior: number;
name: string;
clouds: MetarCloud[];
};

View File

@ -0,0 +1,16 @@
import type { Metar } from '$lib/api/types/metar';
export const aviationWeatherApi = {
/**
* Fetches METAR data for a given ICAO code.
* @param icao - The ICAO code (e.g., "EGKK")
* @returns A Promise resolving to a Metar object or null if not found.
*/
async fetchMetar(icao: string): Promise<Metar | null> {
const url = `/api/metar/${encodeURIComponent(icao)}`;
const response = await fetch(url);
if (!response.ok) throw new Error(`Failed to fetch METAR: ${response.statusText}`);
const data = await response.json();
return data as Metar | null;
}
};

View File

@ -10,7 +10,7 @@
let icaoInput = $state<string>('');
$effect(() => {
// Watch for changes to icaoInput and update the store if exactly 4 chars
// Watch for changes to icaoInput and update the store if exactly 4 chars
if (icaoInput.length === 4) {
icao.set(icaoInput);
} else {
@ -18,8 +18,6 @@
}
});
let { children } = $props();
</script>
@ -30,7 +28,7 @@
class="text-xxl flex w-full items-center justify-center gap-10 px-8 py-4 font-semibold text-green-600"
>
<a href="/" class="hover:underline">METAR &#9925;</a>
<a href="/taf" class="hover:underline">TAF &#9748;</a>
<a href="/taf" class="hover:underline">TAF &#9748;</a>
</nav>
<div class="container mx-auto flex flex-col items-center justify-center">
@ -39,10 +37,7 @@
<Card>
<h2 slot="headerLeft">METARIUS</h2>
<h2 slot="headerRight">Start Here</h2>
<form
slot="content"
class="mx-auto flex w-full max-w-3xl flex-col gap-4 text-lg"
>
<form slot="content" class="mx-auto flex w-full max-w-3xl flex-col gap-4 text-lg">
<div class="flex-space-between flex flex-col gap-10 md:flex-row">
<div class="flex-1">
<p>

View File

@ -5,6 +5,32 @@
import Loading from '$lib/components/Loading.svelte';
import { icao } from '$lib/stores/icao';
import { aviationWeatherApi } from '$lib/api/weatherservice';
import type { Metar } from '$lib/api/types/metar'
import { onDestroy } from 'svelte';
let metar: Metar | null = null;
let loading = false;
let error: string | null = null;
let unsubscribe = icao.subscribe(async (val) => {
metar = null;
error = null;
if (val && val.length === 4) {
loading = true;
try {
const result = await aviationWeatherApi.fetchMetar(val);
metar = result;
if (!metar) error = 'No METAR found for this ICAO code.';
} catch (e) {
error = e instanceof Error ? e.message : 'Unknown error';
} finally {
loading = false;
}
}
});
onDestroy(() => unsubscribe());
</script>
<div style="display: none;">
@ -22,8 +48,22 @@
<h2 slot="headerLeft">METAR</h2>
<h2 slot="headerRight">Results</h2>
<div slot="content" class="flex h-96 w-full items-center justify-center">
{#if $icao}
<span class="text-3xl font-bold">ICAO: {$icao}</span>
{#if loading}
<Loading />
{:else if error}
<span class="text-red-600 text-xl">{error}</span>
{:else if metar}
<div class="text-left text-lg space-y-2">
<div><strong>ICAO:</strong> {metar.icaoId}</div>
<div><strong>Airport:</strong> {metar.name}</div>
<div><strong>Observed:</strong> {metar.reportTime}</div>
<div><strong>Temperature:</strong> {metar.temp}°C</div>
<div><strong>Dew Point:</strong> {metar.dewp}°C</div>
<div><strong>Wind:</strong> {metar.wdir}° at {metar.wspd}kt</div>
<div><strong>Visibility:</strong> {metar.visib}</div>
<div><strong>Pressure:</strong> {metar.altim} hPa</div>
<div><strong>Raw METAR:</strong> <code>{metar.rawOb}</code></div>
</div>
{:else}
<Loading />
{/if}

View File

@ -0,0 +1,19 @@
import type { RequestHandler } from '@sveltejs/kit';
import type { Metar } from '$lib/api/types/metar';
export const GET: RequestHandler = async ({ params }) => {
const icao = params.icao?.toUpperCase();
if (!icao || icao.length !== 4) {
return new Response(JSON.stringify({ error: 'Invalid ICAO' }), { status: 400 });
}
const url = `https://aviationweather.gov/api/data/metar?ids=${icao}&format=json`;
const res = await fetch(url);
if (!res.ok) {
return new Response(JSON.stringify({ error: 'Failed to fetch METAR' }), { status: 502 });
}
const data: Metar[] = await res.json();
return new Response(JSON.stringify(data[0] ?? null), {
headers: { 'Content-Type': 'application/json' }
});
};