A modern web application for managing comedy open mic signups. Built with Next.js, Postgres (via Kysely), and Resend.
Mostly written by AI tools, including this README! Except this line. A human wrote this line. If the rest of the README sounds a little formal, it's because a computer wrote it. I know, it's weird.
- Real-time comedian slot counter
- Sign up as either a comedian or audience member
- Email confirmation and reminder system with timezone-aware date formatting
- Easy signup cancellation via email link
- Responsive design
- Live slot counter via lightweight polling
- Date-specific open mic events and signups
- Active date management system
- Encourage users to support the venue with a purchase
- Admin check-in system with early/on-time/late tracking
- Weighted lottery system for fair performer selection
The app includes a weighted lottery system for selecting performers:
Ticket Weights (1, 3, or 5 tickets):
- Base: 1 ticket for being checked in
- Early bird bonus: +2 tickets for first 5 signups
- Early check-in bonus: +2 tickets for checking in early
Late Handling:
- Late arrivals are always drawn after on-time/early people
- Late people are ordered by lateness (least late first)
Testing:
npm run test:lottery- Clone the repo and install dependencies:
git clone https://github.com/yourusername/openmic.git
cd openmic
npm install- Set up your environment variables in
.env.local:
# Database — any Postgres. Use the host's pooled connection string in production.
# For a local Postgres without TLS, set DATABASE_SSL=disable.
DATABASE_URL=postgres://user:password@host:5432/dbname
# Admin auth
ADMIN_PASSWORD=choose-a-strong-password
ADMIN_SESSION_SECRET=long-random-string-used-to-sign-the-admin-cookie
# Email Configuration
RESEND_API_KEY=your_resend_api_key
# Application Configuration
NEXT_PUBLIC_APP_URL=http://localhost:3003
NEXT_PUBLIC_MAX_COMEDIAN_SLOTS=20
# Google Sheets Sync (optional)
GOOGLE_SERVICE_ACCOUNT_KEY='{ "your": "service account json" }'
GOOGLE_SHEET_ID=your_google_sheet_id- Run the development server:
npm run devWorks with any Postgres (Neon is a good free choice for Vercel). To start fresh:
- Create a Postgres database and copy its connection string into
DATABASE_URL. - Apply the schema:
npm run db:migrate # development (reads .env.local)
npm run db:migrate:prod # production (reads .env.production)Both ends are stock Postgres, so the data copies over directly (Supabase's auth/storage/RLS are skipped):
SUPABASE_DB_URL=<supabase-direct-connection-string> \
NEON_DB_URL=<new-postgres-connection-string> \
npm run db:migrate:from-supabaseThe database is never exposed to the browser — only the server holds DATABASE_URL,
and all access goes through server-side API routes. Authorization lives in those routes:
- Public endpoints (signup, validate-email, slot count, active date): intentionally public.
- Cancellation: authorized by possession of the unguessable signup UUID in the email link.
- Admin: a single password (
ADMIN_PASSWORD) is exchanged at/api/admin/loginfor a signed, httpOnly session cookie (ADMIN_SESSION_SECRET); every admin route requires that cookie.
The application handles email case sensitivity gracefully to ensure a consistent user experience. When users enter emails like Test@example.com vs test@example.com, the system treats them as the same person. This is achieved through a dual approach: emails are normalized to lowercase on the frontend before API calls (preventing future mixed-case data), while the backend compares lower(email) to handle existing mixed-case data. This solution avoids the need for database migrations while ensuring both old and new users experience consistent behavior.
The application properly handles timezone differences for date formatting in emails. Email confirmations display "tonight", "tomorrow", or specific dates based on the event's timezone rather than the server's timezone, ensuring users see accurate relative dates regardless of where the server is hosted.
The application can track new comedian emails by creating date-specific Google Sheets after each open mic. This allows you to review new comedians and manually add them to your main email list.
-
Create a Google Cloud service account:
- Go to Google Cloud Console
- Create a new project or select an existing one
- Enable the Google Sheets API:
- Go to "APIs & Services" → "Library"
- Search for "Google Sheets API" and click on it
- Click "Enable"
- Create a service account:
- Go to "APIs & Services" → "Credentials"
- Click "Create Credentials" → "Service account"
- Give it a name (e.g., "openmic-sheets-sync")
- Click "Create and Continue"
- Skip the optional permissions step (click "Continue")
- Skip the optional user access step (click "Done")
- Create a key for the service account:
- Click on your new service account
- Go to the "Keys" tab
- Click "Add Key" → "Create new key"
- Choose "JSON" format
- Download the JSON file (keep this secure!)
-
Create and configure your Google Sheet:
- Create a new Google Sheet
- Add "Email" as a column header in row 1 (the script will look for this column)
- Get the Sheet ID from the URL:
https://docs.google.com/spreadsheets/d/{SHEET_ID}/edit - Share the sheet with your service account:
- Click "Share" button
- Paste the service account email (found in the JSON file as
client_email) - Give it "Editor" permission
- Click "Send"
-
Set environment variables:
-
Add to your
.env.local:GOOGLE_SERVICE_ACCOUNT_KEY='{ paste contents of downloaded JSON file here }' GOOGLE_SHEET_ID=your_sheet_id_here -
Important: The JSON must be on a single line. You can use a tool like
jq -c . < key.jsonto compact it. -
For GitHub Actions, add these as repository secrets:
- Go to Settings → Secrets and variables → Actions
- Add
GOOGLE_SERVICE_ACCOUNT_KEY(paste the entire JSON content) - Add
GOOGLE_SHEET_ID
-
-
Manual sync:
npm run sync:comedians
-
Automatic daily check:
- The GitHub Actions workflow runs daily at 1pm UTC (8am EST / 9am EDT)
- It checks if there was an open mic yesterday (based on Eastern Time)
- If yes, it creates a new sheet with any new comedian emails
- You can also trigger it manually from the GitHub Actions tab
The sync process:
- Runs automatically the day after each open mic (or can be triggered manually)
- Checks your main Google Sheet for existing emails
- Finds any new comedian emails from last night's open mic
- Creates a new Google Sheet named "New Comedians - [date]" with only the new emails
- You can then manually review and add these to your main sheet as needed
The app is optimized for Vercel deployment. Just push to GitHub and import your repo in Vercel. Make sure to add all your environment variables in the Vercel project settings.
npm run dev- Start development servernpm run build- Build for productionnpm run test- Run testsnpm run db:migrate- Apply schema migrations to the development databasenpm run db:migrate:prod- Apply schema migrations to the production databasenpm run db:migrate:from-supabase- Copy data from an existing Supabase databasenpm run sync:comedians- Manually check for new comedians and create review sheetnpm run test:lottery- Test lottery algorithm against live data
This project is licensed under the MIT License. See the LICENSE file for details.