From df5d8efeb1ec3ccb66ad23887633f40ee32814e4 Mon Sep 17 00:00:00 2001 From: Hein Date: Wed, 12 Aug 2026 19:21:14 +0200 Subject: [PATCH] feat(api): add endpoint to fetch guest list * implement ListGuests method in store * create handleGuestList function in API * add GuestListEntry and GuestList interfaces in UI * create guest list page to display RSVPs --- server/internal/api/handlers.go | 33 ++++++++++++++++++ server/internal/store/store.go | 24 +++++++++++++ ui/src/lib/api.ts | 18 ++++++++++ ui/src/routes/+layout.svelte | 26 ++++++++++---- ui/src/routes/guests/+page.svelte | 57 +++++++++++++++++++++++++++++++ 5 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 ui/src/routes/guests/+page.svelte diff --git a/server/internal/api/handlers.go b/server/internal/api/handlers.go index 87ccf25..a6e9f8b 100644 --- a/server/internal/api/handlers.go +++ b/server/internal/api/handlers.go @@ -33,6 +33,7 @@ func (s *Server) Routes(mux *http.ServeMux) { mux.HandleFunc("GET /healthz", s.handleHealthz) mux.HandleFunc("GET /api/config", s.handleConfig) mux.HandleFunc("POST /api/rsvp", s.handleRSVP) + mux.HandleFunc("GET /api/guests", s.handleGuestList) mux.HandleFunc("POST /api/photos/upload", s.handlePhotoUpload) } @@ -108,6 +109,38 @@ func (s *Server) handleRSVP(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusCreated, map[string]bool{"ok": true}) } +type guestResponse struct { + Name string `json:"name"` + IsChild bool `json:"isChild"` +} + +func (s *Server) handleGuestList(w http.ResponseWriter, r *http.Request) { + guests, err := s.store.ListGuests() + if err != nil { + log.Printf("guests: list failed: %v", err) + writeError(w, http.StatusInternalServerError, "could not load guests") + return + } + + out := make([]guestResponse, 0, len(guests)) + adults, children := 0, 0 + for _, g := range guests { + out = append(out, guestResponse{Name: g.Name, IsChild: g.IsChild}) + if g.IsChild { + children++ + } else { + adults++ + } + } + + writeJSON(w, http.StatusOK, map[string]any{ + "guests": out, + "total": len(out), + "adults": adults, + "children": children, + }) +} + var allowedImageTypes = map[string]bool{ "image/jpeg": true, "image/png": true, diff --git a/server/internal/store/store.go b/server/internal/store/store.go index 80900fd..74d6fcb 100644 --- a/server/internal/store/store.go +++ b/server/internal/store/store.go @@ -78,6 +78,30 @@ type RSVP struct { Message string } +// ListGuests returns every RSVP'd guest, ordered by submission then entry order. +func (s *Store) ListGuests() ([]Guest, error) { + rows, err := s.db.Query( + `SELECT g.name, g.is_child + FROM rsvp_guests g + JOIN rsvps r ON r.id = g.rsvp_id + ORDER BY r.created_at ASC, g.id ASC`, + ) + if err != nil { + return nil, err + } + defer rows.Close() + + var guests []Guest + for rows.Next() { + var g Guest + if err := rows.Scan(&g.Name, &g.IsChild); err != nil { + return nil, err + } + guests = append(guests, g) + } + return guests, rows.Err() +} + func (s *Store) InsertRSVP(r RSVP) error { tx, err := s.db.Begin() if err != nil { diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts index 609c45b..a0a6079 100644 --- a/ui/src/lib/api.ts +++ b/ui/src/lib/api.ts @@ -28,6 +28,18 @@ export interface PhotoUploadPayload { files: FileList; } +export interface GuestListEntry { + name: string; + isChild: boolean; +} + +export interface GuestList { + guests: GuestListEntry[]; + total: number; + adults: number; + children: number; +} + export class ApiError extends Error { constructor( message: string, @@ -62,6 +74,12 @@ export async function submitRsvp(payload: RsvpPayload): Promise { if (!response.ok) throw new ApiError(await parseError(response), response.status); } +export async function fetchGuests(fetchImpl: typeof fetch = fetch): Promise { + const response = await fetchImpl('/api/guests'); + if (!response.ok) throw new ApiError(await parseError(response), response.status); + return response.json(); +} + export async function uploadPhotos(payload: PhotoUploadPayload): Promise { const form = new FormData(); form.set('name', payload.name); diff --git a/ui/src/routes/+layout.svelte b/ui/src/routes/+layout.svelte index cd0fdb6..162e57a 100644 --- a/ui/src/routes/+layout.svelte +++ b/ui/src/routes/+layout.svelte @@ -24,12 +24,12 @@ - Zoé & Shaldon are getting married - + Zoé & Shaldon are getting married +