From fa1a5aff1f36f5e35ce3e6a5cf019ab1ad47a7d7 Mon Sep 17 00:00:00 2001 From: Luke Else Date: Wed, 18 Jun 2025 21:19:09 +0100 Subject: [PATCH 1/4] FEAT: Added METAR Page and updated code to update parameters when URL is sufficient enough --- src/routes/+layout.svelte | 30 ++++++++++++++++++++++++++++-- src/routes/+page.svelte | 12 +++++++++++- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index a5b6a2e..d40dba1 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,6 +5,33 @@ import Section from '$lib/components/Section.svelte'; import Card from '$lib/components/Cards/Card.svelte'; + import { goto } from '$app/navigation'; + + // ICAO input state handler + let icao = $state(''); + $effect(() => { + const params = new URLSearchParams(); + + if (icao.length === 4) { + // If the ICAO code is exactly 4 characters long, add it to the URL parameters + if (icao) params.set('icao', icao); + + // This will trigger a navigation and reload the page data from the server + goto(`?${params.toString()}`, { + keepFocus: true, + replaceState: true, + noScroll: true + }); + } else { + // Otherwise, clear the parameter + goto(`?${params.toString()}`, { + keepFocus: true, + replaceState: true, + noScroll: true + }); + } + }); + let { children } = $props(); @@ -27,8 +54,6 @@
@@ -46,6 +71,7 @@ maxlength="4" required placeholder="EGKK" + bind:value={icao} />
diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 36cf2ca..1b1216a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -3,6 +3,12 @@ import Card from '$lib/components/Cards/Card.svelte'; import { toasts } from 'svelte-toasts'; import Loading from '$lib/components/Loading.svelte'; + + import { page } from '$app/stores'; + import { derived } from 'svelte/store'; + + // Get the ICAO parameter from the URL + const icao = derived(page, ($page) => $page.url.searchParams.get('icao')); @@ -21,7 +27,11 @@

METAR

Results

- + {#if $icao} + ICAO: {$icao} + {:else} + + {/if}
\ No newline at end of file -- 2.49.0 From 215f4a0e2a690568f9c186a9f23f23910fea225a Mon Sep 17 00:00:00 2001 From: Luke Else Date: Wed, 18 Jun 2025 21:24:16 +0100 Subject: [PATCH 2/4] FEAT: Implemented ICAO into svelte stores --- src/lib/stores/icao.ts | 3 +++ src/routes/+layout.svelte | 32 ++++++++++---------------------- src/routes/+page.svelte | 7 +------ 3 files changed, 14 insertions(+), 28 deletions(-) create mode 100644 src/lib/stores/icao.ts diff --git a/src/lib/stores/icao.ts b/src/lib/stores/icao.ts new file mode 100644 index 0000000..8da2fcf --- /dev/null +++ b/src/lib/stores/icao.ts @@ -0,0 +1,3 @@ +import { writable } from 'svelte/store'; + +export const icao = writable(''); \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index d40dba1..71ec0da 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,33 +5,21 @@ import Section from '$lib/components/Section.svelte'; import Card from '$lib/components/Cards/Card.svelte'; - import { goto } from '$app/navigation'; + import { icao } from '$lib/stores/icao'; + + let icaoInput = $state(''); - // ICAO input state handler - let icao = $state(''); $effect(() => { - const params = new URLSearchParams(); - - if (icao.length === 4) { - // If the ICAO code is exactly 4 characters long, add it to the URL parameters - if (icao) params.set('icao', icao); - - // This will trigger a navigation and reload the page data from the server - goto(`?${params.toString()}`, { - keepFocus: true, - replaceState: true, - noScroll: true - }); + // Watch for changes to icaoInput and update the store if exactly 4 chars + if (icaoInput.length === 4) { + icao.set(icaoInput); } else { - // Otherwise, clear the parameter - goto(`?${params.toString()}`, { - keepFocus: true, - replaceState: true, - noScroll: true - }); + icao.set(''); } }); + + let { children } = $props(); @@ -71,7 +59,7 @@ maxlength="4" required placeholder="EGKK" - bind:value={icao} + bind:value={icaoInput} /> diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 1b1216a..9941a42 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -4,14 +4,9 @@ import { toasts } from 'svelte-toasts'; import Loading from '$lib/components/Loading.svelte'; - import { page } from '$app/stores'; - import { derived } from 'svelte/store'; - - // Get the ICAO parameter from the URL - const icao = derived(page, ($page) => $page.url.searchParams.get('icao')); + import { icao } from '$lib/stores/icao'; -
{toasts.add({ title: 'Welcome', -- 2.49.0 From 155f91c240df8fccc17b9e738a959732afa2ce30 Mon Sep 17 00:00:00 2001 From: Luke Else Date: Wed, 18 Jun 2025 21:46:32 +0100 Subject: [PATCH 3/4] FEAT: Added ability to query weatherservice API --- src/lib/api/types/metar.ts | 42 ++++++++++++++++++++++++ src/lib/api/weatherservice.ts | 16 ++++++++++ src/routes/+layout.svelte | 11 ++----- src/routes/+page.svelte | 44 ++++++++++++++++++++++++-- src/routes/api/metar/[icao]/+server.ts | 19 +++++++++++ 5 files changed, 122 insertions(+), 10 deletions(-) create mode 100644 src/lib/api/types/metar.ts create mode 100644 src/lib/api/weatherservice.ts create mode 100644 src/routes/api/metar/[icao]/+server.ts diff --git a/src/lib/api/types/metar.ts b/src/lib/api/types/metar.ts new file mode 100644 index 0000000..1e4991e --- /dev/null +++ b/src/lib/api/types/metar.ts @@ -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[]; +}; \ No newline at end of file diff --git a/src/lib/api/weatherservice.ts b/src/lib/api/weatherservice.ts new file mode 100644 index 0000000..bf84f89 --- /dev/null +++ b/src/lib/api/weatherservice.ts @@ -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 { + 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; + } +}; \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 71ec0da..97e27ea 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -10,7 +10,7 @@ let icaoInput = $state(''); $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(); @@ -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" > METAR ⛅ - TAF ☔ + TAF ☔
@@ -39,10 +37,7 @@

METARIUS

Start Here

- +

diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 9941a42..f1da9ad 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -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());

@@ -22,8 +48,22 @@

METAR

Results

- {#if $icao} - ICAO: {$icao} + {#if loading} + + {:else if error} + {error} + {:else if metar} +
+
ICAO: {metar.icaoId}
+
Airport: {metar.name}
+
Observed: {metar.reportTime}
+
Temperature: {metar.temp}°C
+
Dew Point: {metar.dewp}°C
+
Wind: {metar.wdir}° at {metar.wspd}kt
+
Visibility: {metar.visib}
+
Pressure: {metar.altim} hPa
+
Raw METAR: {metar.rawOb}
+
{:else} {/if} diff --git a/src/routes/api/metar/[icao]/+server.ts b/src/routes/api/metar/[icao]/+server.ts new file mode 100644 index 0000000..2e7dec1 --- /dev/null +++ b/src/routes/api/metar/[icao]/+server.ts @@ -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' } + }); +}; \ No newline at end of file -- 2.49.0 From 85bed825fc04481492e925324bafc258daa534e7 Mon Sep 17 00:00:00 2001 From: Luke Else Date: Wed, 18 Jun 2025 21:48:26 +0100 Subject: [PATCH 4/4] CHORE: Remove in-required tests from the application --- src/demo.spec.ts | 7 ------- src/routes/page.svelte.test.ts | 11 ----------- 2 files changed, 18 deletions(-) delete mode 100644 src/demo.spec.ts delete mode 100644 src/routes/page.svelte.test.ts diff --git a/src/demo.spec.ts b/src/demo.spec.ts deleted file mode 100644 index e07cbbd..0000000 --- a/src/demo.spec.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest'; - -describe('sum test', () => { - it('adds 1 + 2 to equal 3', () => { - expect(1 + 2).toBe(3); - }); -}); diff --git a/src/routes/page.svelte.test.ts b/src/routes/page.svelte.test.ts deleted file mode 100644 index a110662..0000000 --- a/src/routes/page.svelte.test.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { describe, test, expect } from 'vitest'; -import '@testing-library/jest-dom/vitest'; -import { render, screen } from '@testing-library/svelte'; -import Page from './+page.svelte'; - -describe('/+page.svelte', () => { - test('should render h1', () => { - render(Page); - expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument(); - }); -}); -- 2.49.0