2 Commits
10 changed files with 18 additions and 4 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ RSVP is a party: one or more guest names attached to a single RSVP + optional sh
| Method | Path | Body | Notes |
|---|---|---|---|
| POST | `/api/rsvp` | `{names: string[], message?}` | at least one non-blank name required; insert party + guests, email couple |
| GET | `/api/config` | — | `{weddingDate, ceremonyTime, rsvpByDate, venueName, venueAddress, rsvpPhone}` (all RFC3339 where dates) — single source of truth for countdowns, gate, and displayed details |
| GET | `/api/config` | — | `{weddingDate, eventEnd, ceremonyTime, rsvpByDate, venueName, venueAddress, rsvpPhone}` (all RFC3339 where dates) — single source of truth for countdowns, calendar, gate, and displayed details |
| POST | `/api/photos/upload` | multipart: `name, message?, email?, phone?, files[]` | rejected with 403 if `now < weddingDate`, **enforced server-side** regardless of client clock |
| GET | `/healthz` | — | liveness |
+1 -1
View File
@@ -42,7 +42,7 @@ docker-compose.yml wedding app, published to 127.0.0.1:8080 only
```
## Config
Copy `config.example.yaml` to `config.yaml` and fill in the wedding date, SMTP settings, storage path, and (if enabled) the Origin service key. Path is resolved from `CONFIG_PATH` (default `./config.yaml`) — that's the only env var; everything else lives in the file.
Copy `config.example.yaml` to `config.yaml` and fill in the wedding date, event end time, SMTP settings, storage path, and (if enabled) the Origin service key. `wedding.event_end` is the explicit local RFC3339 end time used for calendar downloads. Path is resolved from `CONFIG_PATH` (default `./config.yaml`) — that's the only env var; everything else lives in the file.
When `origin.service_key` is configured, the server registers itself with Origin as the `generic` service type named `ZoeShaldon` immediately at startup and repeats the check-in every 24 hours. Registration failures are logged without taking down the wedding site. Leave the key empty to disable registration.
+1
View File
@@ -11,6 +11,7 @@ site:
wedding:
date: "2026-11-07T16:00:00+02:00" # RFC3339, guest arrival time; drives countdown + photo upload gate
event_end: "2026-11-07T23:30:00+02:00" # RFC3339, local end time used by calendar downloads
ceremony_time: "16:00 for 16:30" # display text
rsvp_by: "2026-10-01T08:00:00+02:00" # RFC3339, RSVP deadline; drives the RSVP countdown
venue_name: "Rivier Plaas Wedding Venue"
+1
View File
@@ -58,6 +58,7 @@ func (s *Server) handleConfig(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, map[string]string{
"siteUrl": s.cfg.Site.URL,
"weddingDate": s.cfg.Wedding.Date.Format(time.RFC3339),
"eventEnd": s.cfg.Wedding.EventEnd.Format(time.RFC3339),
"ceremonyTime": s.cfg.Wedding.CeremonyTime,
"rsvpByDate": s.cfg.Wedding.RSVPBy.Format(time.RFC3339),
"venueName": s.cfg.Wedding.VenueName,
+4
View File
@@ -35,6 +35,7 @@ type SiteConfig struct {
type WeddingConfig struct {
Date time.Time `yaml:"date"`
EventEnd time.Time `yaml:"event_end"`
CeremonyTime string `yaml:"ceremony_time"`
RSVPBy time.Time `yaml:"rsvp_by"`
VenueName string `yaml:"venue_name"`
@@ -112,6 +113,9 @@ func Load(path string) (*Config, error) {
if cfg.Wedding.RSVPBy.IsZero() {
return nil, fmt.Errorf("wedding.rsvp_by is required in %s", path)
}
if cfg.Wedding.EventEnd.IsZero() {
return nil, fmt.Errorf("wedding.event_end is required in %s", path)
}
return &cfg, nil
}
+1
View File
@@ -1,6 +1,7 @@
export interface WeddingConfig {
siteUrl: string;
weddingDate: string;
eventEnd: string;
ceremonyTime: string;
rsvpByDate: string;
venueName: string;
+1
View File
@@ -2,6 +2,7 @@
// fallback while /api/config loads (and when developing the UI without the Go server).
export const FALLBACK_SITE_URL = "https://zoeshaldon.warky.info";
export const FALLBACK_WEDDING_DATE = "2026-11-07T16:00:00+02:00";
export const FALLBACK_EVENT_END = "2026-11-07T23:30:00+02:00";
export const FALLBACK_CEREMONY_TIME = "16:00 for 16:30";
export const FALLBACK_RSVP_BY_DATE = "2026-10-17T00:00:00+02:00";
export const FALLBACK_VENUE_NAME = "Rivier Plaas Wedding Venue";
+6
View File
@@ -3,6 +3,7 @@ import { fetchConfig } from './api';
import {
FALLBACK_SITE_URL,
FALLBACK_WEDDING_DATE,
FALLBACK_EVENT_END,
FALLBACK_CEREMONY_TIME,
FALLBACK_RSVP_BY_DATE,
FALLBACK_VENUE_NAME,
@@ -13,6 +14,7 @@ import {
export function createWeddingInfo() {
let siteUrl = $state(FALLBACK_SITE_URL);
let date = $state(new Date(FALLBACK_WEDDING_DATE));
let eventEnd = $state(new Date(FALLBACK_EVENT_END));
let ceremonyTime = $state(FALLBACK_CEREMONY_TIME);
let rsvpByDate = $state(new Date(FALLBACK_RSVP_BY_DATE));
let venueName = $state(FALLBACK_VENUE_NAME);
@@ -25,6 +27,7 @@ export function createWeddingInfo() {
.then((config) => {
siteUrl = config.siteUrl;
date = new Date(config.weddingDate);
eventEnd = new Date(config.eventEnd);
ceremonyTime = config.ceremonyTime;
rsvpByDate = new Date(config.rsvpByDate);
venueName = config.venueName;
@@ -46,6 +49,9 @@ export function createWeddingInfo() {
get ceremonyTime() {
return ceremonyTime;
},
get eventEnd() {
return eventEnd;
},
get rsvpByDate() {
return rsvpByDate;
},
+1 -1
View File
@@ -128,7 +128,7 @@
{/if}
</div>
<main
class="text-shadow-sm text-shadow-white/80 dark:text-shadow-black/70 flex flex-1 flex-col"
class="text-surface-900 dark:text-surface-100 text-shadow-sm text-shadow-white/80 dark:text-shadow-black/70 flex flex-1 flex-col"
>
{@render children()}
</main>
+1 -1
View File
@@ -8,7 +8,7 @@
const icsUrl = $derived(
buildIcsDataUrl({
start: wedding.date,
end: new Date(wedding.date.getTime() + 7.5 * 60 * 60 * 1000),
end: wedding.eventEnd,
summary: "Zoé & Shaldon's Wedding",
location: `${wedding.venueName}, ${wedding.venueAddress}`,
description: `Join us as we say I do! ${wedding.ceremonyTime}.`,