Skip to content

Repository files navigation

Parking Reservation

Web app for reserving office parking spots, backed by SpacetimeDB. Built with Qwik and QwikCity.

Prerequisites

  • Node.js ^18.17.0 || ^20.3.0 || >=21.0.0
  • A Google Cloud project with OAuth 2.0 credentials
  • Optionally a GitHub OAuth app, to offer GitHub sign-in as well
  • SpacetimeDB table

Environment Variables

Copy the example file and fill in your credentials:

cp .env.example .env
Variable Description Required
GOOGLE_CLIENT_ID Google OAuth 2.0 Client ID Yes
GOOGLE_CLIENT_SECRET Google OAuth 2.0 Client Secret Yes
GITHUB_CLIENT_ID GitHub OAuth app Client ID No
GITHUB_CLIENT_SECRET GitHub OAuth app Client Secret No
OAUTH_REDIRECT_URI Callback URL for both providers, localhost by default No
PUBLIC_SPACETIMEDB_MODULE Database name Yes
PUBLIC_SPACETIMEDB_URI Database cluster Yes
API_KEYS Comma-separated API keys for REST API access No

Sign-in

Users sign in with Google or with GitHub; both providers land on the same /api/auth/callback route and the provider is chosen with /api/auth?provider=github. Leave the GITHUB_* variables unset and the GitHub button simply will not work.

Sign-in is not an access control — the parking data is public by design. It exists to deter bots and to prefill the reservation name, so any Google or GitHub account is accepted. Reservations are keyed by the display name the provider returns (name, falling back to the @login for GitHub accounts with no name set), so signing in through a different provider than usual can produce a different name.

Project Structure

├── public/              Static assets
├── src/
│   ├── components/      Shared components
│   ├── routes/          Directory-based routing (pages + API endpoints)
│   ├── services/        Google Sheets & OAuth helpers
│   ├── plugins/         Fastify server plugin
│   ├── entry.ssr.tsx    SSR entry point
│   └── entry.fastify.tsx  Production server entry point
├── adapters/
│   └── fastify/         Fastify adapter Vite config
└── scripts/             One-off maintenance scripts

Importing the legacy spreadsheet

Before SpacetimeDB the reservations lived in a Google Spreadsheet with one tab per year. scripts/migrate-from-sheet.mjs imports a year of that spreadsheet into the database, treating the spreadsheet as the source of truth — it overwrites the spots and every reservation on the dates the sheet covers.

node scripts/migrate-from-sheet.mjs --year 2026           # dry run: report only
node scripts/migrate-from-sheet.mjs --year 2026 --apply   # write to the database
node scripts/migrate-from-sheet.mjs --help                # all flags

Requires the spacetime CLI, logged in (spacetime login). Run the dry run first — it prints the spot list, date range and reservation count it parsed, plus any rows it could not make sense of.

What it does not touch: reservations on dates outside the sheet (pass --wipe to clear those too), and spots the sheet no longer lists (pass --remove-missing-spots to delete those and all of their reservations).

The reducers the script calls are restricted to the identities listed in ADMIN_IDENTITIES in spacetimedb/src/index.ts — reducers are callable by any client that can reach the module, so a "delete every reservation" reducer cannot be left open. If a call is rejected, the error names the calling identity; add it there and re-publish. These reducers exist only for this import and can be deleted once it is done.

Development

npm install
npm start

The dev server runs at http://localhost:5173 with SSR and hot module replacement.

Production Build

npm run build

This generates dist/ (client assets) and server/ (Fastify server bundle). To run it locally:

ORIGIN=http://localhost:3000 npm run serve

The ORIGIN environment variable is required for CSRF protection and must match the URL users access the app from.

Docker

Build and run with Docker

docker build -t nice-parking .
docker run -p 3000:3000 --env-file .env -e ORIGIN=https://your-domain.com nice-parking

Docker Compose (recommended)

# Start in the background
docker compose up -d

# With a custom origin
ORIGIN=https://your-domain.com docker compose up -d

# View logs
docker compose logs -f

# Stop
docker compose down

The compose file reads credentials from your .env file and defaults ORIGIN to http://localhost:3000 if not set.

REST API

A REST API (/api/v1) is available for AI agent and programmatic access. Authentication is via Bearer token — set the API_KEYS environment variable with one or more comma-separated keys.

  • AI agent guidance: /llms.txt — plain-language overview and workflow tips
  • OpenAPI spec: /openapi.json — machine-readable specification for tool generation

Production Checklist

  • Set ORIGIN to your actual domain (e.g. https://parking.example.com) -- required for CSRF protection
  • Set OAUTH_REDIRECT_URI to your production callback URL (https://your-domain.com/api/auth/callback) — it defaults to the localhost one, so a deployment that leaves it unset will bounce users to localhost:5173
  • Register that same URL in your Google Cloud Console and as the Authorization callback URL of your GitHub OAuth app
  • Ensure outbound HTTPS access to accounts.google.com, googleapis.com, github.com and api.github.com

Contributors

Languages