Skip to content
davedownunderPublic

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Booking

A Calendly-style booking page. Visitors pick 30, 45 or 60 minutes, see only the times you're actually free, and get a Google Meet link, a calendar invite and a confirmation email from your Gmail — plus a link to move or cancel the meeting themselves.

There's no database. Google Calendar is the state: free/busy decides what's offered, and creating the event is the booking. Availability is re-checked server-side at the moment of booking, so two people racing for the same slot can't both win.

Changing how you take meetings

Everything is in src/config.ts — working hours, timezone, the three meeting lengths, buffer between meetings, minimum notice, how far ahead the calendar opens, a daily cap, and specific days off. Edit, commit, push; Vercel redeploys.

Working hours are expressed in your own timezone (America/New_York), so daylight saving takes care of itself.

Setting it up

You need a Google Cloud OAuth client and a refresh token. This is a one-time job.

1. Create the Google Cloud project

  1. Go to https://console.cloud.google.com/ and create a project (call it anything).

  2. APIs & Services → Library: enable the Google Calendar API and the Gmail API.

  3. APIs & Services → OAuth consent screen: choose External, fill in the app name and your email, and add these scopes:

    • .../auth/calendar.readonly
    • .../auth/calendar.events
    • .../auth/gmail.send
  4. Fill in Branding, then publish the app. Google won't let an External app leave Testing until Branding has an app home page, a privacy policy link and a terms of service link, all on a domain listed under Authorised domains — and that domain has to be verified in Search Console. This app serves all three pages itself, so once it's deployed at your own domain, point Branding at:

    Field Value
    Application home page https://<your-domain>/
    Privacy policy https://<your-domain>/privacy
    Terms of service https://<your-domain>/terms
    Authorised domain your registered domain (covers subdomains)

    Then set the publishing status to In production. This matters: while the app sits in Testing, Google expires refresh tokens after 7 days and bookings start failing a week after you set it up. Since you're the only person who ever signs in, the "unverified app" warning you'll click past once is harmless.

    Deploy first (step 4 below), then come back and finish this — the URLs have to resolve.

  5. APIs & Services → Credentials → Create credentials → OAuth client ID, type Web application. Under Authorized redirect URIs add exactly:

    http://localhost:53682/callback
    

    Copy the client ID and secret.

2. Get a refresh token

cp .env.example .env.local

Put the client ID and secret in .env.local — or, easier, download the client's JSON from the console (the ⬇ button on the credential) and let the script read it:

npm run import-client

It picks up the newest client_secret*.json in ~/Downloads on its own, fills in both values without printing the secret, and says straight away if the client is the wrong type or missing the redirect URI. Pass a path if the file is somewhere else. Then:

npm run token

That opens Google's consent screen, you sign in as the account that owns the calendar, and it writes GOOGLE_REFRESH_TOKEN straight into .env.local. It deliberately doesn't print the token, so it can't end up in terminal scrollback, a screen recording or an agent transcript — pass --print if you actually need it on screen.

The token grants calendar and send-mail access to your account — treat it like a password. Copy it into Vercel from .env.local yourself.

3. Run it

npm run dev

4. Deploy

Push to GitHub, import the repo on Vercel, and add these under Settings → Environment Variables: GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN, OWNER_NAME, OWNER_EMAIL, and SITE_URL.

SITE_URL needs to be the real address (e.g. https://book.example.com), because it's baked into the reschedule links inside emails and invites — left unset, a booking made through a Vercel preview URL would hand out a link that dies with that deployment.

No redirect URI is needed in production; the app only ever refreshes a token it already has.

What a booking does

  1. POST /api/book re-reads your free/busy and rejects the slot if it's gone.
  2. Creates the event on your calendar with the guest as an attendee and a Google Meet link, and lets Google send the invite.
  3. Sends your own confirmation email from your Gmail, with the Meet link and the time in the guest's timezone.

If step 3 fails the booking still stands — the guest is told to use the invite instead. Every event carries a bookedVia=booking-app marker, which is how the daily cap counts what came in through the page without touching the rest of your calendar.

Rescheduling and cancelling

Every booking gets a /manage/<token> link, which goes into the confirmation email and into the calendar invite's description — so it's findable from either. The guest can move the meeting to any other free slot or cancel it outright, and you get the calendar update either way.

The token is the event id plus an HMAC of it, so a link works with no login and can't be edited into someone else's booking. There's still nothing stored anywhere: the page reads the event back off your calendar to build itself.

Rescheduling patches the event rather than replacing it, so the Meet link and the invite thread survive the move, and the meeting being moved is excluded from its own availability check — otherwise its buffer would block the times either side of it. Meetings that have already started can't be changed from the link.

Tests

npm test

Covers the scheduling rules on their own — working hours, buffers, notice, the horizon, the daily cap, and a daylight-saving switch — with no network involved.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages