adding sse for the landing page to update turnament stats for everyone

This commit is contained in:
Sebastian 2025-10-16 13:09:02 +02:00
parent c61bcedb3a
commit af4dbdea91
5 changed files with 63 additions and 7 deletions

View file

@ -64,7 +64,7 @@ async fn main() -> Result<(), rocket::Error> {
let rocket = rocket::build() let rocket = rocket::build()
.manage(state) .manage(state)
.mount("/", routes![healthz, login, logout, events]) .mount("/", routes![healthz, login, logout, events, public_events])
.mount("/persons", routes::persons::routes()) .mount("/persons", routes::persons::routes())
.mount("/tournament", routes::tournaments::routes()); .mount("/tournament", routes::tournaments::routes());
@ -157,3 +157,25 @@ fn events(_user: AuthUser, state: &State<AppState>) -> EventStream![Event + '_]
} }
} }
} }
#[get("/events/public")]
fn public_events(state: &State<AppState>) -> EventStream![Event + '_] {
let mut receiver = state.event_sender.subscribe();
EventStream! {
loop {
match receiver.recv().await {
Ok(event) => {
match &event {
AppEvent::TournamentUpserted { .. } | AppEvent::TournamentDeleted { .. } => {
yield Event::json(&event);
}
_ => continue,
}
}
Err(RecvError::Closed) => break,
Err(RecvError::Lagged(_)) => continue,
}
}
}
}

View file

@ -5,13 +5,13 @@ export type AppEvent =
| { type: 'tournament_upserted'; tournament: TournamentInfo } | { type: 'tournament_upserted'; tournament: TournamentInfo }
| { type: 'tournament_deleted'; tournament_id: number }; | { type: 'tournament_deleted'; tournament_id: number };
export function listenToEvents(onEvent: (event: AppEvent) => void) { export function listenToEvents(onEvent: (event: AppEvent) => void, endpoint = '/api/events') {
let stopped = false; let stopped = false;
let source: EventSource | null = null; let source: EventSource | null = null;
function connect() { function connect() {
if (stopped) return; if (stopped) return;
source = new EventSource('/api/events'); source = new EventSource(endpoint);
source.onmessage = (event) => { source.onmessage = (event) => {
try { try {
const data = JSON.parse(event.data) as AppEvent; const data = JSON.parse(event.data) as AppEvent;

View file

@ -5,10 +5,16 @@ export type TournamentEvent =
| { type: 'tournament_upserted'; tournament: TournamentInfo } | { type: 'tournament_upserted'; tournament: TournamentInfo }
| { type: 'tournament_deleted'; tournament_id: number }; | { type: 'tournament_deleted'; tournament_id: number };
type TournamentEventOptions = {
endpoint?: string;
};
export function listenToTournamentEvents( export function listenToTournamentEvents(
onUpsert: (tournament: TournamentInfo) => void, onUpsert: (tournament: TournamentInfo) => void,
onDelete: (tournamentId: number) => void onDelete: (tournamentId: number) => void,
options: TournamentEventOptions = {}
) { ) {
const endpoint = options.endpoint ?? '/api/events';
return listenToEvents((event) => { return listenToEvents((event) => {
if (event.type === 'tournament_upserted') { if (event.type === 'tournament_upserted') {
onUpsert(event.tournament); onUpsert(event.tournament);
@ -17,5 +23,5 @@ export function listenToTournamentEvents(
if (event.type === 'tournament_deleted') { if (event.type === 'tournament_deleted') {
onDelete(event.tournament_id); onDelete(event.tournament_id);
} }
}); }, endpoint);
} }

View file

@ -63,8 +63,10 @@
return `${count} ${count === 1 ? 'spelare' : 'spelare'}`; return `${count} ${count === 1 ? 'spelare' : 'spelare'}`;
} }
onMount(() => { onMount(() => {
const stop = listenToTournamentEvents(upsertTournament, removeTournament); const stop = listenToTournamentEvents(upsertTournament, removeTournament, {
endpoint: '/api/public-events'
});
return () => { return () => {
stop(); stop();
}; };

View file

@ -0,0 +1,26 @@
import type { RequestHandler } from './$types';
import { proxyRequest } from '$lib/server/backend';
export const GET: RequestHandler = async (event) => {
const { response, setCookies } = await proxyRequest(event, '/events/public', {
method: 'GET'
});
const headers = new Headers();
const contentType = response.headers.get('content-type');
if (contentType) {
headers.set('content-type', contentType);
} else {
headers.set('content-type', 'text/event-stream');
}
for (const cookie of setCookies) {
headers.append('set-cookie', cookie);
}
headers.set('cache-control', 'no-cache');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers
});
};