krz/moonrise

Night-sky companion for iPhone, Apple Watch and Vision Pro: what's up tonight, the Moon, planets, launches and missions, and a Library of the solar system. SwiftUI app with a Cloudflare Workers backend. astronomy cloudflare-workers ios night-sky space swiftui visionos watchos

Commit 0c3cae6f42

0c3cae6f429205798ad4218a68308377f2c19917

parent: 7da97cdb74

Unsigned

cmc <hello@cleberg.net> · 2026-10-18 04:02 UTC

Keep last good feed snapshot on failed polls (!15)

- Poll failures write =ll2:error= / =swpc:error= rows instead of overwriting the feed's row.
- Before: a failed LL2 poll (HTTP 429 from Cloudflare's shared egress IPs) set =ok = 0= on =ll2=, so =/v1/launches= returned =fetchedAt: null= while still serving 20 launches; a failed SWPC poll would have hidden space weather entirely.

=node --test=: 10 pass. Deployed after merge.

Layout: unified · split

server/src/index.ts +3 −2
@@ -98,13 +98,14 @@ async function launches(env: Env, now: Date): Promise<Response> {
98 }); 98 });
99} 99}
100 100
101// Failures are recorded under "<feed>:error" so the last good snapshot and its time survive.
101async function pollLaunches(env: Env, now: Date): Promise<void> { 102async function pollLaunches(env: Env, now: Date): Promise<void> {
102 let list: Launch[]; 103 let list: Launch[];
103 try { 104 try {
104 list = await fetchUpcoming(env.LL2_API_KEY); 105 list = await fetchUpcoming(env.LL2_API_KEY);
105 } catch (e) { 106 } catch (e) {
106 console.error("ll2", String(e)); 107 console.error("ll2", String(e));
107 await env.DB.prepare("INSERT INTO feed_snapshots (feed, fetched_at, ok) VALUES ('ll2', ?1, 0) ON CONFLICT (feed) DO UPDATE SET fetched_at = ?1, ok = 0").bind(now.toISOString()).run(); 108 await env.DB.prepare("INSERT INTO feed_snapshots (feed, fetched_at, ok) VALUES ('ll2:error', ?1, 0) ON CONFLICT (feed) DO UPDATE SET fetched_at = ?1").bind(now.toISOString()).run();
108 return; 109 return;
109 } 110 }
110 const upsert = env.DB.prepare( 111 const upsert = env.DB.prepare(
@@ -126,7 +127,7 @@ async function pollSpaceWeather(env: Env, now: Date): Promise<void> {
126 w = await fetchSpaceWeather(); 127 w = await fetchSpaceWeather();
127 } catch (e) { 128 } catch (e) {
128 console.error("swpc", String(e)); 129 console.error("swpc", String(e));
129 await env.DB.prepare("INSERT INTO feed_snapshots (feed, fetched_at, ok) VALUES ('swpc', ?1, 0) ON CONFLICT (feed) DO UPDATE SET fetched_at = ?1, ok = 0").bind(now.toISOString()).run(); 130 await env.DB.prepare("INSERT INTO feed_snapshots (feed, fetched_at, ok) VALUES ('swpc:error', ?1, 0) ON CONFLICT (feed) DO UPDATE SET fetched_at = ?1").bind(now.toISOString()).run();
130 return; 131 return;
131 } 132 }
132 await env.DB.prepare( 133 await env.DB.prepare(