Most attendees arrive without their QR open, and taking money at the
door meant leaving the scanner for the event dashboard, where the Add
Ticket modal demanded an email and recorded no payment method. The
screen now leads with manual name search, keeps the camera one tap away
behind a fullscreen overlay, and creates and charges walk-ins inline.
Check-in and payment are one action: anything done here is born
confirmed, paid (or comp) and checked in through a single endpoint,
POST /api/events/:eventId/door-checkin. There are no confirm dialogs
anywhere, because they stall the queue; a ten-second Undo replaces
them, reversing exactly what the action changed via the undo state
recorded alongside its idempotency key. Writes fire in the background
with retries, so venue wifi never blocks the person at the door, and a
capacity limit only warns, since staff at the door are the authority.
Every write carries a client-generated idempotency key, inserted in the
same transaction as the writes it guards, so a double tap or a retry
after a timeout cannot produce a second ticket, payment or check-in.
Search runs entirely in memory over one preloaded list: names are
matched accent- and case-insensitively in both directions, per word,
prefix before substring, with a mostly-numeric query searching phone
digits so two people with the same name can be told apart.
Door money is recorded as payments.source 'door' plus payments.method
(cash, bitcoin, transfer or guest) while provider keeps its existing
value, so capacity counting, the stale-booking sweeps and the admin
payment lists are unaffected and revenue can still be split pre-sale
versus door. Bitcoin records the payment as made, on the same trust
model as cash, with no invoice generated; lib/doorPayments.ts is where
a real Lightning flow slots in later.
Also fixes the SQLite tickets DDL, which still created the pre-split
attendee_name column with NOT NULL email and phone. Only fresh
databases were affected -- existing ones were relaxed by later ALTERs
-- but on those, door walk-ins (and any other ticket) could not be
inserted at all.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the hand-rolled JWT auth with Better Auth 1.6.25 httpOnly cookie
sessions, validated against the database on every request so revocation,
bans and role changes take effect immediately.
Backend:
- betterAuth.ts wires the Drizzle adapter, magic links, Google sign-in and
the admin plugin; auth-schema.ts maps Better Auth's models onto the
existing `users` table so user IDs and their foreign keys survive intact.
- routes/auth.ts is gone; Better Auth serves the standard endpoints and
authExt.ts carries the flows it doesn't cover.
- auth.ts shrinks to session resolution and helpers; sessions/revocation in
dashboard.ts now read and delete `auth_sessions` rows directly.
- Schema adds the Better Auth core + admin columns (email_verified, image,
banned, ban_reason, ban_expires), with migrations and tests.
- rateLimit.ts resolves client IPs spoof-resistantly: proxy headers are only
honoured from loopback/RFC1918 peers plus TRUSTED_PROXIES.
- passwordPolicy.ts centralises password validation.
- Bump drizzle-orm, drizzle-kit and better-sqlite3 to versions compatible
with Better Auth.
Frontend:
- auth-client.ts plus a reworked AuthContext and api/client.ts move to
cookie-based sessions; no more bearer tokens in requests or middleware.
photo-api:
- Validate Better Auth session cookies against the shared auth_sessions
table instead of verifying JWTs; JWT_SECRET is no longer needed for user
auth, and PHOTO_VIEW_SECRET now signs gallery view tokens.
BETTER_AUTH_SECRET and BETTER_AUTH_URL are required in production; the
deprecated JWT_SECRET stays only as the photo-api view-token fallback.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace manualProviders.ts with a paymentProviders.ts registry
(automatic vs manual settlement) and move all seat counting into
capacity.ts as the single source of truth: only paid/checked-in tickets
and pending_approval payments hold a seat, so abandoned checkouts never
block sales. Admins can now knowingly approve a payment over capacity
(allowOverCapacity), with the booking and admin UIs updated to match.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Split oversized frontend API client, email service, and admin/booking pages into focused modules while preserving import surfaces, and add Redis-backed queues, stale booking cleanup, stronger auth, and scale deployment configs.
Co-authored-by: Cursor <cursoragent@cursor.com>