Commit Graph
127 Commits
Author SHA1 Message Date
Michilis 183a2c99ed Merge pull request 'Restrict whole-event door takings to admin and organizer.' (#35) from dev into main
Reviewed-on: #35
2026-08-23 06:38:57 +00:00
MichilisandClaude Opus 5 745af4184f Restrict whole-event door takings to admin and organizer.
The door session sheet showed every staff member what the event had
taken overall, by tender and against pre-sale. That is management
information, not door information, and it matches the convention
already applied to the other revenue aggregates (admin/analytics,
admin/export/financial are both admin-only).

Door staff keep their own shift cash-up: the "This session" totals are
computed on the device from its own action log, so nothing they need to
reconcile at the end of the night is lost.

The gate is on GET /api/events/:eventId/door-summary, not only on the
section that renders it -- hiding the panel while the endpoint still
returned the figures would leave them one network response away. The
client skips the request entirely for staff rather than provoking a 403.

The test auth mock previously waved every role through, so it could not
have caught a wrong gate; it now honours the role list, which also puts
several already-written assertions onto real code paths.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:35:57 +00:00
Michilis 279cd19308 Merge pull request 'Fix the backend build against hono's narrowed route param type.' (#34) from dev into main
Reviewed-on: #34
2026-08-23 06:16:27 +00:00
MichilisandClaude Opus 5 02a12ee9e0 Fix the backend build against hono's narrowed route param type.
npm audit fix floated hono from 4.4 to 4.11 within the ^4.4.7 range and
tsc stopped compiling. c.req.param(key) has two overloads: it returns
string only when the route path's literal type survives inference, and
string | undefined otherwise. requireAuth() returns a handler annotated
with a bare Context, which erases the path generic, so every param read
behind it is now string | undefined.

Only four sites failed. Everywhere else the value lands in
eq((table as any).id, ...), where the any swallows it. Routes that also
run zValidator kept their typing, since that middleware is generic and
restores the inference -- door.ts:251 compiles while the two plain
requireAuth routes beside it do not.

loadEvent now accepts string | undefined and returns null for a missing
id, which both door callers already handle with their 404 branch. The
payments and tickets handlers pass the id off the row they just fetched
and null-checked rather than the raw param.

This leaves the underlying erasure in place. Typing requireAuth to
preserve the path generic would restore string on every authed route,
but it touches all of them and belongs in its own change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:15:28 +00:00
Michilis de791fd17d Merge pull request 'Dev' (#33) from dev into main
Reviewed-on: #33
2026-08-23 06:04:26 +00:00
MichilisandClaude Opus 5 ed1d3a8c12 Paginate the bookings page and the event attendee/ticket tabs.
Extract the pagination controls shared by the events and users lists into
components/admin/Pagination, along with a usePaginatedList helper that slices a
list and keeps the page in range when the list shrinks under it.

Bookings, the Attendees tab and the Tickets tab paginate client-side rather than
on the server: each already loads its full set for figures a slice would break —
the bookings stat cards, the group-booking totals and the sibling payment-method
lookup, and the per-status counts the two tabs share with the event's other tabs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 06:01:27 +00:00
MichilisandClaude Opus 5 87bf9a6151 Fix Google sign-in for existing email and ticket-buyer accounts.
Google sign-in only worked for people who already had a linked google
row in auth_accounts. Anyone who first appeared another way -- a guest
ticket purchase, or an email/password signup made after the Better Auth
migration -- got a 401 "account not linked".

trustedProviders: ['google'] defeats only one of better-auth's two
linking gates. The second, requireLocalEmailVerified, defaults to true
and refuses the link whenever the LOCAL users.email_verified is false,
independently of whether the provider is trusted. That flag is false for
every guest-booking row and for every post-migration signup, since
requireEmailVerification is off and no verification mail is sent.

Turn that gate off: the Google id_token is signature-verified against
Google's JWKS with issuer/audience/max-age checks and carries its own
email_verified, so the local column proves nothing extra here.

Linking alone was not enough. getAuthUser() rejects any session whose
user is not 'active', so a ticket buyer would link Google, receive a
cookie, and still look logged out. A databaseHooks.account.create.after
hook now promotes unclaimed rows to claimed/active when a google account
is attached, scoped in the WHERE clause so a suspended account is never
reactivated this way.

Also normalize users.email. The unique index is case-sensitive while
better-auth lowercases every lookup, so someone who booked as
John@Gmail.com was invisible to sign-in and Google minted a SECOND user
row, stranding their tickets on the first. normalizeEmail() covers the
find-or-create sites in tickets.ts and door.ts plus the claim-eligibility
lookup, and an idempotent migration lowercases existing rows -- skipping
any that would collide and reporting those for manual merge, since
merging two people's tickets and payments is not a migration's call.
tickets.attendeeEmail still stores the address exactly as typed.

Tests drive the real signInSocial id-token path with Google stubbed by
signing tokens with a throwaway RS256 key and serving our own JWKS, so
the actual verification runs without network or credentials. That also
makes the deprecation risk loud: requireLocalEmailVerified is marked for
removal upstream, and an upgrade that drops it now fails CI instead of
silently locking ticket buyers out again.

Frontend carries error.code through so OAUTH_LINK_ERROR renders an
actionable message in both locales rather than a bare "account not
linked".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 05:31:02 +00:00
MichilisandClaude Opus 5 3f7b2d51db Redesign the PDF ticket around the branded card layout.
The old ticket was a centred stack of Helvetica on white that read as a
receipt: the QR sat in open space, the event, attendee and code lines
were indistinguishable at a glance, and nothing on the page identified
Spanglish beyond a text heading. The page is now a full-bleed card --
orange rule, cream field, navy footer -- with the logo and event title
in the header, the QR raised into a white rounded panel above its code,
and labelled venue and ticket holder blocks below it, so door staff can
find the code and the name without reading the page.

The ticket is bilingual, driven by the ticket's preferredLanguage: the
labels, the terms line, the Spanish event title and the date format all
follow it, with 24h time and day-first ordering in Spanish. Events
store the venue as one string, so the text before the first comma is
treated as the venue name and the remainder as its address.

Layout adapts rather than overflowing: long titles wrap to two lines at
a smaller size, and the QR panel flexes so the detail block always
lands just above the footer. The single and combined generators were
copies of each other and now share one page renderer, with multi-ticket
bookings marked by a small counter in the panel.

The logo ships as backend/assets/logo-spanglish.png, resolved from both
src/lib and dist/lib, falling back to the frontend copy and then to a
text wordmark, so a deployment that misses the asset still produces a
ticket.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 05:16:41 +00:00
MichilisandClaude Opus 5 5970d707af Paginate the admin events list.
The admin events page fetched every event on each load and rendered
them all, which grows without bound as the archive fills up. GET
/api/events now takes optional page and pageSize parameters and returns
total alongside the rows; pagination is opt-in, so the public pages and
the admin filter dropdowns that pass neither still get the full list
and the untouched response shape.

Page size is selectable (10/25/50/100) and deleting the last event on a
page falls back to the new last page instead of showing an empty table.
The ?edit=<id> deep link no longer depends on the target being in the
current page: when it is missing from the loaded rows the event is
fetched directly, guarded by a ref so the modal opens once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 05:16:28 +00:00
MichilisandClaude Opus 5 e296e80e48 Rebuild the Scanner page into a unified door check-in screen.
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>
2026-08-22 06:09:55 +00:00
MichilisandClaude Opus 5 a0161a67d2 Add "paid at the door" ticket type to the Add Ticket modal.
Door walk-ins were only expressible as an unpaid ticket, which left the
cash out of revenue. The new type records the cash payment as paid and
makes every field optional, since a walk-in often gives no details:
a blank name is logged as "Walk-in", and a confirmation email only goes
out when an email is entered.

Door tickets reuse paymentStatus 'paid' (the column enum is capped at
paid/unpaid/comp) so badges and revenue totals pick them up with no
migration; the cash payment row is referenced "Paid at door" to keep
them distinguishable from emailed manual tickets.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 04:39:54 +00:00
Michilis d06f80e572 Merge pull request 'Add mobile save sheet with same-origin photo downloads.' (#32) from dev into main
Reviewed-on: #32
V1.5
2026-08-06 20:20:15 +00:00
MichilisandCursor be4dd5b47f Add mobile save sheet with same-origin photo downloads.
Phones cannot land downloads in the photo library, so the gallery opens a share sheet for a cacheable preview while streaming via a dedicated download endpoint and recording preview sizes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 20:14:31 +00:00
Michilis e0f0b2f1e9 Merge pull request 'Dev' (#31) from dev into main
Reviewed-on: #31
2026-08-05 23:18:50 +00:00
MichilisandCursor 390e1dc0ea Hide admin payment summary cards when privacy mode is enabled.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 23:15:24 +00:00
MichilisandCursor fa8686276d Share gallery layout with loading skeletons and add download progress.
Keep the public gallery hero/masonry geometry stable across route and client loading, and show busy state while photos download.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-05 22:05:33 +00:00
Michilis 498d7d8a7d Add photo content-hash dedup and local↔S3 library sync.
Uploads skip per-gallery duplicates, checksums can be backfilled, and STORAGE_BACKEND plus sync tooling make switching storage backends safe.
2026-08-05 05:41:12 +00:00
Michilis 82857b5aa8 Merge pull request 'Fix post-login redirect when the session cookie is off-origin' (#30) from dev into main
Reviewed-on: #30
2026-07-29 20:46:11 +00:00
MichilisandClaude Opus 4.6 dafa3711f8 Fix post-login redirect when the session cookie is off-origin
Signing in showed the "Welcome back!" toast but never left /login. The
session cookie was host-only on the API subdomain, so the Next middleware
guard on the site origin saw no cookie and bounced /dashboard straight
back to /login?redirect=/dashboard.

- Add AUTH_COOKIE_DOMAIN, wiring Better Auth's crossSubDomainCookies so
  the cookie also reaches the site origin. Unset in dev, where localhost
  is single-host and must stay host-only.
- Navigate after authentication with a full page load, via a shared
  authRedirect helper: only a top-level request carries the httpOnly
  cookie. Used by the login, register, magic-link and Google flows.
- Show "Redirecting..." on the login and register pages and keep the
  submit button disabled until the browser replaces the page, instead of
  re-enabling it mid-navigation.
- Guard against a redirect loop with a sessionStorage marker. A React ref
  cannot do this: the full page load resets component state. If the
  destination bounces back, explain it rather than navigating again.
- Middleware: accept any *.session_token cookie so a cookiePrefix change
  cannot lock everyone out, and preserve the destination's query string.
- Trust any loopback port in dev, so reaching the dev server through a
  forwarded port does not fail Better Auth's CSRF origin check.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-29 20:45:05 +00:00
Michilis 5b478c327d Merge pull request 'Dev' (#29) from dev into main
Reviewed-on: #29
2026-07-29 19:08:28 +00:00
MichilisandClaude Opus 5 733d2459df Migrate authentication to Better Auth
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>
2026-07-29 19:07:04 +00:00
MichilisandClaude Opus 5 4afa5d6fa0 Show when the booking was made in the verify-payment modal
The modal only had "User marked as paid", which is absent for manual
payments the customer never confirmed — those opened with no timestamp
at all. Show the payment's createdAt unconditionally as "Booking made",
and give both lines a relative age suffix.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:06:39 +00:00
MichilisandClaude Opus 4.6 617c884012 Add view tokens for non-public gallery images and per-mode gate pages.
<img> tags cannot send Authorization headers, so non-public gallery photos
were invisible even to authorized viewers. The server now mints short-lived
HMAC view tokens (gallery-scoped, hour-bucketed) and embeds them in every
file URL for non-public galleries. Access denials return distinct 403
messages per visibility mode, and the frontend renders a matching gate page
(private, link-only, ticket-holders, login prompt) with an inline login
modal so visitors never leave the gallery page.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-26 06:48:05 +00:00
MichilisandClaude Opus 4.8 93476ac72a Document photo galleries, payment providers, and updated deploy topology in README
Cover features added since the last README update: the standalone photo-api
Go service, the central payment provider registry (Lightning + manual
TPago/bank/card/cash), stable /next and /featured URLs, dashboard overview,
and the three-service dev/deploy setup (photo-api on 3003 dev / 3020 prod).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 05:57:30 +00:00
MichilisandClaude Fable 5 c4094630d9 Add a thumbnail filmstrip to the photo lightbox.
- Render a horizontally scrollable strip of thumbnails below the image
  that highlights and auto-centres the active photo and lets you jump
  straight to any shot.
- Pass thumbUrl through from the public and admin galleries, falling
  back to previewUrl when no thumbnail exists.
- Restructure the overlay into a flex column (image area + strip) and
  require a clearly horizontal gesture before treating a touch as a
  prev/next swipe, so vertical scrolls no longer trigger navigation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 05:53:37 +00:00
Michilis 6ec8b8400f Merge pull request 'dev' (#28) from dev into main
Reviewed-on: #28
2026-07-26 05:38:23 +00:00
MichilisandClaude Fable 5 9b2668f498 Unify admin ticket creation into one modal with first-class payment status.
- Replace the three Attendees-tab modals (Manual Ticket / Add at Door /
  Invite Guest) with a single Add Ticket modal: Paid/Unpaid/Guest segmented
  control, shared fields, "Check in now" for all types, and a live
  "what happens" preview, backed by one POST /api/tickets/admin/add.
- Add tickets.payment_status (paid | unpaid | comp) with a backfill
  migration; keep it in sync on every payment-settlement path (mark-paid,
  admin approval, Lightning, free bookings, hold recovery).
- Show Paid/Unpaid/Comp badges in the attendee list, count only paid
  tickets toward revenue, let unpaid tickets be resolved via Mark Paid,
  and flag unpaid tickets with their balance due in the door scanner.
- Replace the per-page useStatsPrivacy hook with an admin-wide
  PrivacyContext + SensitiveValue mask, toggled from the admin layout.
- Add server-side pagination with page-size options to the users page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 05:25:00 +00:00
MichilisandClaude Fable 5 e38d14970d Harden the Redis layer for production.
- Locks no longer fail open: an unreachable backend throws
  LockUnavailableError and withLock skips the run (or opts into running
  unlocked, as template seeding does). The lnbits payment poller keeps
  polling through an outage, made safe by only sending confirmation
  emails when a row actually transitioned.
- Rate limiter INCR+PEXPIRE now runs as one Lua script, self-healing
  counters stranded without a TTL.
- Per-email login lockout moves to a Redis-backed store shared across
  replicas (in-memory fallback preserved), case-insensitive on email.
- Graceful shutdown on SIGTERM/SIGINT: stop periodic jobs, close the
  server, force-close lingering SSE sockets, close Redis.
- Active PING probe backs the health flag; /health reports last ping.
  SSE payment streams also poll the DB as a pub/sub-gap fallback.
- Scale compose gains requirepass, maxmemory 256mb with noeviction, and
  an authenticated healthcheck; .env.example documents passwords, TLS
  (rediss://), and DB-index selection.
- First tests in the repo: vitest + ioredis-mock covering the lock,
  rate limiter, and login lockout stores (27 tests).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 04:58:52 +00:00
MichilisandClaude Fable 5 71c277045b Centralize seat capacity accounting and the payment provider registry.
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>
2026-07-26 04:58:40 +00:00
MichilisandCursor c9a600b6d6 Add photo galleries API and frontend for public and admin viewing.
Introduces the Go photo-api service, nginx/systemd deploy wiring, and Next.js gallery/lightbox pages so event photos can be managed and browsed.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-25 17:32:23 +00:00
Michilis 19461098a0 Merge pull request 'Stop auto-failing manual payments (TPago/bank/cash) after the pending TTL.' (#27) from tpago-fix into main
Reviewed-on: #27
2026-07-20 03:55:01 +00:00
MichilisandCursor 4772b85f3d Stop auto-failing manual payments (TPago/bank/cash) after the pending TTL.
Exclude manual providers from booking cleanup, hold them via the 72h sweep instead, and let admins reopen failed payments to pending.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-20 03:53:51 +00:00
Michilis 74dcc8e5ac Merge pull request 'Normalize RUC formatting to base-checkdigit across booking and admin views.' (#26) from RUC-fix into main
Reviewed-on: #26
1.4
2026-07-13 03:48:13 +00:00
MichilisandCursor 0d47156071 Normalize RUC formatting to base-checkdigit across booking and admin views.
Accept dashed or digits-only RUC input on the backend, store a canonical dashed form, and display it consistently in booking and admin UIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-13 03:47:35 +00:00
Michilis bd1043a3f4 Merge pull request 'speed' (#25) from speed into main
Reviewed-on: #25
2026-07-13 01:18:23 +00:00
MichilisandCursor 75e317d73e Replace spinners with skeleton loading states for faster perceived UX.
Add reusable Skeleton components and route-level loading files across public pages and admin lists so layouts stay stable while data loads.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 23:14:11 +00:00
MichilisandCursor d8c207c995 Add Next.js as a root dependency for the monorepo workspace.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 22:05:22 +00:00
MichilisandCursor cb422332c8 Fix booking defaults, dashboard alerts, admin edit flow, and ended-event payments.
Require an explicit payment method on booking, hide stale release banners for past or inactive events, open event editing in place on the detail page, and auto-reject unconfirmed payments after events end without sending email.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-12 22:04:22 +00:00
MichilisandCursor 8d5fbf18b4 Fix event time save off-by-one using bundled tz data.
Use moment-timezone in parseEventDatetime so naive America/Asuncion wall-clock times convert correctly regardless of the host Node tz database. Also normalize user registration date filters with toDbDate for Postgres.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-03 01:51:18 +00:00
MichilisandCursor 78ffd0ae52 Allow Lightning invoice reuse and re-payment from the dashboard.
Store LNbits invoice data on payments, add an invoice endpoint that reuses valid invoices or regenerates expired ones, and wire the booking payment page to fetch and display invoices via a shared watcher hook.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-01 16:48:28 +00:00
MichilisandClaude Sonnet 5 09bfb17f03 Add custom error and global-error pages matching site design
Adds app/error.tsx (client error boundary, reuses Header/Footer/Button)
and app/global-error.tsx (root layout crash fallback with self-contained
html/body and manually replicated styling), both bilingual (ES/EN) and
matching the brand's colors, fonts, and tone.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-01 06:06:22 +00:00
Michilis cacc52ec24 Security recovery: hold sweep, dashboard updates, and admin fixes. 2026-07-01 05:51:38 +00:00
Michilis 107cd9753e Merge pull request 'Redesign user dashboard with overview tab and i18n payment copy.' (#24) from dashboard-update into main
Reviewed-on: #24
2026-06-29 17:00:15 +00:00
MichilisandCursor 38526f17b5 Redesign user dashboard with overview tab and i18n payment copy.
Consolidate profile and security into AccountTab, add shared dashboard components, and move awaiting-approval payment messages to translations.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 07:39:52 +00:00
Michilis 194604004e Merge pull request 'security-update' (#23) from security-update into main
Reviewed-on: #23
2026-06-29 06:47:19 +00:00
MichilisandCursor f5fe87613f Add stable /next and /featured URLs that redirect to the current event.
These short links always resolve to the latest upcoming or featured event so they can be shared without updating when events rotate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-27 05:35:52 +00:00
MichilisandCursor 613bd7be1d Refactor monolithic modules and harden booking, email, and auth infrastructure.
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>
2026-06-25 07:12:59 +00:00
MichilisandCursor f0e2de2834 Fix Lightning payment UI not updating after LNbits invoice is paid.
Ensure the booking page receives payment confirmation via a streaming SSE proxy, parallel status polling, and more reliable backend event delivery.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-25 04:15:02 +00:00
MichilisandCursor a6840ea953 Harden auth, payments, and frontend against review findings.
Close exploitable gaps in booking/payment flows, enforce token versioning and account checks, gate sensitive payment data, and add middleware plus input validation across admin routes.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-24 19:59:02 +00:00
Michilis bb920ce6f0 Merge pull request 'Add per-quantity TPago payment links for multi-ticket checkout.' (#22) from tpago-update into main
Reviewed-on: #22
2026-06-19 00:24:00 +00:00