{"id":50706129,"url":"https://github.com/noahpro99/gnwedding2026","last_synced_at":"2026-06-09T12:01:28.703Z","repository":{"id":361529027,"uuid":"1254765837","full_name":"noahpro99/gnwedding2026","owner":"noahpro99","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-08T07:26:02.000Z","size":21928,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-08T09:04:45.179Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/noahpro99.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-31T01:23:36.000Z","updated_at":"2026-06-08T07:26:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/noahpro99/gnwedding2026","commit_stats":null,"previous_names":["noahpro99/gnwedding2026"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/noahpro99/gnwedding2026","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noahpro99%2Fgnwedding2026","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noahpro99%2Fgnwedding2026/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noahpro99%2Fgnwedding2026/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noahpro99%2Fgnwedding2026/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/noahpro99","download_url":"https://codeload.github.com/noahpro99/gnwedding2026/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/noahpro99%2Fgnwedding2026/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34105565,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-09T12:01:27.563Z","updated_at":"2026-06-09T12:01:28.697Z","avatar_url":"https://github.com/noahpro99.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Noah \u0026 G — Wedding 2026\n\nA wedding website. Server-rendered React via **TanStack Start**, **Bun**\nruntime, **Tailwind CSS v4**, and a tiny **SQLite** database for RSVPs.\n\n---\n\n## Tech stack\n\n| Concern        | Choice                                  |\n| -------------- | --------------------------------------- |\n| Runtime        | Bun (`bun` v1.x)                        |\n| SSR framework  | TanStack Start (React 19)               |\n| Routing        | TanStack Router (file-based)            |\n| Styling        | Tailwind CSS v4 (`@tailwindcss/vite`)   |\n| Database       | SQLite via `bun:sqlite` (no native deps)|\n| Build          | Vite 6                                  |\n| Deploy target  | Docker container on `noahpi` (Linux/ARM)|\n\nServer functions (`createServerFn`) handle form submissions, so there's no\nseparate REST/API layer to maintain. Everything that touches the DB lives in\n`src/server/`.\n\n---\n\n## Project layout\n\n```\nsrc/\n├── routes/                  file-based routes (TanStack Router)\n│   ├── __root.tsx           shared shell: \u003chtml\u003e, nav, footer\n│   ├── index.tsx            homepage (hero + quick links + album CTA)\n│   ├── save-the-date.tsx    card with PNG frame + RSVP CTA\n│   ├── invite.$id.tsx       invite page; \"Accept/Decline\" reveals RSVP form\n│   ├── rsvp.tsx             standalone RSVP page\n│   ├── our-story.tsx        timeline\n│   ├── travel.tsx           hotels + shuttle request form\n│   ├── itinerary.tsx        Google Calendar embed\n│   ├── wedding-party.tsx    bridal party grid\n│   ├── registry.tsx         outbound registry links\n│   └── faq.tsx              accordion\n├── components/\n│   ├── SiteNav.tsx          sticky top nav (responsive)\n│   ├── SiteFooter.tsx\n│   ├── SectionHeader.tsx\n│   ├── CardFrame.tsx        PNG-decoration card wrapper (transparent center)\n│   └── RsvpForm.tsx         shared RSVP form (yes/no + party + dietary…)\n├── server/\n│   ├── db.ts                bun:sqlite connection + schema bootstrap\n│   ├── db-init.ts           seed script: inserts demo invite\n│   └── rsvp.ts              createServerFn endpoints (RSVP, transport, invite lookup)\n├── router.tsx               router instance\n├── routeTree.gen.ts         AUTO-GENERATED by TanStack plugin\n└── styles.css               Tailwind import + theme tokens\npublic/\n├── hero.jpg                 homepage hero (REPLACE)\n└── card-frame.png           invite card decoration with transparent middle (REPLACE)\ndata/                        SQLite file lives here (gitignored, Docker volume)\n```\n\n---\n\n## Local development\n\n```bash\n# one-time\nbun install\n\n# run dev server (SSR + HMR)\nbun run dev\n# → http://localhost:3000\n\n# optional: seed a demo invite at /invite/demo\nbun run db:init\n```\n\n### Things to fill in before you ship\n\nSearch the codebase for `REPLACE` to find every placeholder. Highlights:\n\n- `src/routes/index.tsx` — wedding date, location, photo album URL\n- `src/routes/itinerary.tsx` — Google Calendar embed `src`\n- `src/routes/travel.tsx` — hotel names, addresses, block codes, booking URLs\n- `src/routes/wedding-party.tsx` — names, roles, photos\n- `src/routes/registry.tsx` — registry URLs\n- `src/routes/our-story.tsx` — story milestones\n- `public/hero.jpg` — main homepage photo\n- `public/card-frame.png` — the PNG card decoration (transparent center)\n\n---\n\n## How the invite-only flow works\n\nThere are **two** invite-style pages:\n\n1. `/save-the-date` — generic, share-with-anyone. Card art, date, RSVP/Travel links.\n2. `/invite/$id` — formal invitation **personalized** to a guest. The id maps\n   to a row in the `invites` table (guest names, max party size). Clicking\n   \"Accept / Decline\" reveals the RSVP form pre-filled with their name and\n   party-size cap.\n\nYou generate one invite row per household. The link you send to that household\nis `https://yoursite/invite/\u003cthat-id\u003e`. RSVPs from that page are linked back to\nthe invite via `rsvps.invite_id`.\n\n### Creating invites\n\nFor now, do it from a Bun shell:\n\n```bash\nbun --print \"\n  const { db } = await import('./src/server/db.ts');\n  db.run('INSERT INTO invites (id, guest_names, party_size_max) VALUES (?, ?, ?)',\n    ['smith-family', 'The Smith Family', 4]);\n  console.log('done');\n\"\n```\n\n(A small admin page can be added later if needed.)\n\n---\n\n## Database\n\nSQLite file path is controlled by `DB_PATH` (defaults to `./data/wedding.sqlite`).\nSchema is created on first connection (`src/server/db.ts`). Tables:\n\n- **invites** — `id`, `guest_names`, `party_size_max`\n- **rsvps** — `invite_id?`, `primary_name`, `email`, `attending`, `party_size`,\n  `dietary`, `notes`, `needs_transport`, `created_at`\n- **transport_requests** — separate shuttle requests from `/travel`\n\nTo inspect:\n\n```bash\nsqlite3 data/wedding.sqlite '.dump'\nsqlite3 data/wedding.sqlite 'SELECT * FROM rsvps;'\n```\n\n---\n\n## Deployment — `noahpi`\n\nThe repo is wired for CI deploy via `.github/workflows/deploy.yml`. On every\npush to `main`, the workflow SSHes into `noahpi`, pulls the repo, rebuilds\nthe Docker image, and tells the central `~/docker-compose.yml` to come up.\n\n### Required GitHub secrets\n\n- `SSH_HOST` — Pi hostname or IP\n- `SSH_USERNAME` — login user\n- `SSH_PRIVATE_KEY` — private key authorized on the Pi\n\n### Image\n\n`noahpro/gnwedding2026:latest`. Built from the `Dockerfile` in this repo —\nmulti-stage with `oven/bun:1` for both build and runtime. The runtime stage\nserves both SSR (TanStack Start) and the static `dist/client/` via a thin\n`serve.ts` Bun wrapper. Container listens on `:3000`.\n\n### docker-compose entry on the Pi\n\nLives in `~/docker-compose.yml` alongside the other services. Traefik picks\nup routing via labels — no `ports:` mapping needed.\n\n```yaml\n  gnwedding2026:\n    image: noahpro/gnwedding2026:latest\n    restart: unless-stopped\n    volumes:\n      - ./gnwedding-data:/app/data    # SQLite persists across rebuilds\n    labels:\n      - traefik.enable=true\n      - traefik.http.routers.gnwedding2026.rule=Host(`gnwedding2026.com`)\n      - traefik.http.routers.gnwedding2026.entrypoints=https\n      - traefik.http.routers.gnwedding2026.tls=true\n      - traefik.http.routers.gnwedding2026.tls.certresolver=${CERT_RESOLVER}\n      - traefik.http.services.gnwedding2026.loadbalancer.server.port=3000\n```\n\nPublic host: **gnwedding2026.com** (apex only — there's no `www.` DNS\nrecord, and adding `www.` to the Host rule made Let's Encrypt 429 us out\nfor an hour. Add a `www.` A record then extend the rule with\n`|| Host(\\`www.gnwedding2026.com\\`)` if you want both.)\n\nSQLite file lives at `~/gnwedding-data/wedding.sqlite` and survives\nimage rebuilds.\n\n### Backups\n\nAdd a host-side cron on `noahpi` to snapshot the DB:\n\n```cron\n0 3 * * *  sqlite3 /home/\u003cuser\u003e/gnwedding-data/wedding.sqlite \".backup '/home/\u003cuser\u003e/backups/wedding-$(date +\\%F).sqlite'\"\n```\n\n---\n\n## Scripts reference\n\n| Command            | What it does                                     |\n| ------------------ | ------------------------------------------------ |\n| `bun run dev`      | Vite dev server with SSR + HMR on `:3000`        |\n| `bun run build`    | Production build into `dist/`                    |\n| `bun run start`    | Run the built server (`bun serve.ts`) on `:3000` |\n| `bun run typecheck`| `tsc --noEmit`                                   |\n| `bun run db:init`  | Create tables and insert a demo invite           |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoahpro99%2Fgnwedding2026","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnoahpro99%2Fgnwedding2026","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnoahpro99%2Fgnwedding2026/lists"}