From 64f758e7cbfc12a91a1621eace0d1cca49720f58 Mon Sep 17 00:00:00 2001 From: Tyler Gillispie Date: Thu, 27 Aug 2026 23:41:54 -0700 Subject: [PATCH] fix(events): ensure getEventWithAttendees matches attendees by FK and short username, returns seats_claimed, and updates guest drawer --- public/sessions-scripts.js | 16 ++++++-- src/features/events/events_actions_routes.ts | 4 ++ src/features/events/queries.ts | 6 ++- src/features/sessions/sessions.test.tsx | 40 ++++++++++++++++++++ 4 files changed, 61 insertions(+), 5 deletions(-) diff --git a/public/sessions-scripts.js b/public/sessions-scripts.js index f13f7ee..4a69082 100644 --- a/public/sessions-scripts.js +++ b/public/sessions-scripts.js @@ -735,12 +735,20 @@ async function openAttendeesDrawer(eventId, eventName) { if (res.ok && data.success) { const attendees = data.attendees || []; - const claimedElem = document.getElementById("guestDrawerClaimed"); - if (claimedElem) claimedElem.textContent = attendees.length; - if (data.event) { + const claimedElem = document.getElementById("guestDrawerClaimed"); + if (claimedElem) { + claimedElem.textContent = data.event.seats_claimed !== undefined + ? data.event.seats_claimed + : attendees.length; + } + const maxElem = document.getElementById("guestDrawerMax"); - if (maxElem) maxElem.textContent = data.event.max_seats; + if (maxElem) { + maxElem.textContent = Number(data.event.max_seats) > 0 + ? data.event.max_seats + : "∞"; + } const expiresAtElem = document.getElementById("guestDrawerExpiresAt"); if (expiresAtElem && data.event.expires_at) { diff --git a/src/features/events/events_actions_routes.ts b/src/features/events/events_actions_routes.ts index 5703cc1..3e8daac 100644 --- a/src/features/events/events_actions_routes.ts +++ b/src/features/events/events_actions_routes.ts @@ -216,7 +216,11 @@ eventsActionsRoutes.get("/api/events/:id/attendees", async (c) => { success: true, attendees: data.attendees, event: { + id: data.event.id, + name: data.event.name, + slug: data.event.slug, max_seats: data.event.max_seats, + seats_claimed: data.event.seats_claimed, expires_at: data.event.expires_at, }, }); diff --git a/src/features/events/queries.ts b/src/features/events/queries.ts index bb33fea..ac2c17a 100644 --- a/src/features/events/queries.ts +++ b/src/features/events/queries.ts @@ -237,18 +237,22 @@ export async function getEventWithAttendees( isGlobalAdmin: boolean, ) { const event = await sqlWrapper.sql` - SELECT id, slug, max_seats, expires_at FROM event_passes + SELECT id, name, slug, max_seats, seats_claimed, expires_at FROM event_passes WHERE id = ${eventId} AND (created_by = ${userId} OR ${isGlobalAdmin}) `.then((res: any) => res[0]); if (!event) return null; + const shortId = String(event.id).split("-")[0]; + const shortPattern = `guest_${shortId}_%`; + const attendees = await sqlWrapper.sql` SELECT s.id, s.label, s.is_paused, s.created_at, s.expires_at, s.last_activity_at, s.last_activity_action, u.username, u.display_name FROM sessions s JOIN users u ON s.user_id = u.id WHERE u.event_pass_id = ${eventId} + OR u.username LIKE ${shortPattern} ORDER BY s.created_at DESC `; diff --git a/src/features/sessions/sessions.test.tsx b/src/features/sessions/sessions.test.tsx index f91cda4..48cd533 100644 --- a/src/features/sessions/sessions.test.tsx +++ b/src/features/sessions/sessions.test.tsx @@ -10,6 +10,7 @@ import { EventCockpitDeckFragment, WorkshopPassDrawerFragment, } from "../events/fragments.tsx"; +import { AppLaunchpadPageFragment } from "../dashboard/launchpad_fragments.tsx"; Deno.test("[Sessions] GET /dashboard unauthenticated redirects to /login", async () => { const req = new Request("http://localhost/dashboard"); @@ -155,3 +156,42 @@ Deno.test("[Sessions] GET /dashboard/passkeys unauthenticated redirects to /logi assertEquals(res.status, 302); assertEquals(res.headers.get("location"), "/login"); }); + +Deno.test("[Sessions] AppLaunchpadPageFragment renders authorized workload cards and roles", () => { + const mockApps = [ + { + id: "app-1", + name: "ed-droid", + description: "Autonomous Agent UI", + domain: "ed.atyg.org", + role: "Guest (Viewer)", + }, + { + id: "app-2", + name: "grafana", + description: "Metrics Dashboard", + domain: "grafana.atyg.org", + role: "Admin", + }, + ]; + + const html = ; + const str = String(html); + assertStringIncludes(str, "Application Launchpad"); + assertStringIncludes(str, "ed-droid"); + assertStringIncludes(str, "ed.atyg.org"); + assertStringIncludes(str, "Guest (Viewer)"); + assertStringIncludes(str, "https://ed.atyg.org"); + assertStringIncludes(str, "grafana"); + assertStringIncludes(str, "badge-success"); +}); + +Deno.test("[Sessions] AppLaunchpadPageFragment renders empty state when no apps authorized", () => { + const html = ; + const str = String(html); + assertStringIncludes(str, "No Authorized Applications"); + assertStringIncludes( + str, + "You do not have active role grants for any workloads.", + ); +});