GavL – Next Auctions
Bidding · Stripe Payments (Test Mode) · Postgres Triggers · Realtime Notifications


Real-time bid flow
DB triggers and per-user Supabase Realtime notifications (Next Auctions README).
Overview
Auction platforms need bidding, automatic closure at the deadline, notifications to the people involved, and payment settlement without exposing privileged database access to the browser.
GavL keeps the workflow in Postgres: SECURITY DEFINER triggers track the highest bid and write NEW_BID, NEW_AUCTION_WON and NEW_PAYMENT notifications, pushed to each user over a private Supabase Realtime channel. A pg_cron job calls the close-auctions Edge Function every 5 minutes to close due auctions, and a signature-verified Stripe webhook records payment through a server-only service role.
Technical Architecture
GavL is a learning build focused on real-time full-stack flows. It follows a ports-and-adapters layout: domains hold types and Zod schemas; ports are repository interfaces; services own workflows; Supabase repositories sit behind those ports; instances wire services to infrastructure. Notifications for new bids, auction wins and payments go to each affected user over private per-user Supabase Realtime broadcast channels; other viewers see a new price on refresh. A pg_cron job calls the close-auctions Edge Function every 5 minutes, so auctions close up to 5 minutes after endAt; the repo commits it as a migration that reads its URL and key from Vault, and the live project runs the same job, created in the dashboard. Stripe Checkout plus a signature-verified webhook (raw body, constructEvent) sets paidAt through a service-role client that stays server-only. The UI is multilingual (French, English, German via Lingui), responsive, and uses URL-driven filters, sorting, and pagination (nuqs) with Suspense and skeleton loaders.
Key features
- Bid Tracking in Postgres: Triggers keep the highest bid and bidder on the auction row; a UNIQUE (auction, amount) index rejects tied bids; RLS stops owners from editing or deleting an auction once it has bids.
- End-to-End Auction Lifecycle: Creation, editing, bidding, scheduled closure (Edge Function), winner notifications, and payment completion.
- Stripe Payments: Stripe Checkout with a signature-verified webhook for server-side payment status;
/apiis excluded from the i18n middleware so Stripe is never redirected. - Notification System: Real-time notifications for new bids, auction wins and payments, pushed to each affected user over a private Supabase Realtime channel; the payment notification fires only on the first
paid_atwrite. - Server-Side Table Filtering: URL-driven filters, sorting, and pagination using nuqs for consistent server-client synchronization.
- Multilingual UI: Localized interface in French, English, and German via Lingui with locale-aware routes.
- Ports-and-Adapters Layering: Services behind repository interfaces, with Jest unit tests on services and mappers run in CI (lint, build, coverage); Supabase declarative schemas with diff-generated migrations.
Technologies Used
- Next.js
- TypeScript
- Supabase
- Stripe
- React
- TanStack Table
- React Hook Form
- Zod
- nuqs
- Lingui
- Recharts
- shadcn/ui
- Tailwind CSS
- Jest
- Frontend: Next.js, TypeScript, React
- Backend & Database: Supabase (Postgres triggers, RLS, Realtime, Edge Functions, pg_cron)
- Payments: Stripe Checkout & Webhooks
- UI & Form Handling: shadcn/ui, React Hook Form, Zod
- Filtering & Data Display: nuqs, TanStack Table, Recharts (payments dashboards)
- Internationalization: Lingui: French / English / German with locale-aware routes.
- File Uploads: Uppy
- Testing: Jest unit tests, CI (lint, build, coverage)
- Architecture: Ports and adapters: domains, repository interfaces, services and wired instances
Challenges and Learnings
Designing a bidding flow that stayed understandable while triggers, the scheduled Edge Function and Stripe webhooks all touched related rows. RLS had to protect normal clients while the webhook wrote through a service-role client whose key never reaches the browser. Keeping notifications, auction state and payment flags aligned: the payment trigger fires only when paid_at goes from NULL to set, so a webhook retry does not notify twice.
Outcome
GavL demonstrates an end-to-end auction flow at next-auctions.vercel.app: bidding, scheduled closure, Stripe payments in test mode, real-time notifications, multilingual UI (French, English, German), dashboards with charts, and a ports-and-adapters codebase with Jest unit tests. It complements the Web3 case studies with full-stack work on payments, Postgres triggers and RLS, notifications, i18n and testing.
Key Metrics
- Live demo: next-auctions.vercel.app
- i18n: French / English / German (Lingui)
- Architecture: ports and adapters, services behind repository interfaces; URL-driven filters via nuqs; Jest unit tests in CI
- Automation: pg_cron calls the close-auctions Edge Function every 5 minutes; signature-verified Stripe webhook → server-only service-role write
Transferable patterns: Payments + webhooks, workflow rules in Postgres triggers and RLS, per-user notification channels, and scheduled lifecycle jobs.
Known limitations
- Known bugs, bid integrity: the server does not check that a bid beats the current highest (only the form does); RLS lets bidders edit or delete their own bids while an auction is open; owners can bid on their own auctions; bids are accepted between endAt and the next scheduled close, up to 5 minutes later; and the highest-bid trigger reads the top bid without a lock, so two near-simultaneous bids can store a stale highest bid. The fix is a
place_bidRPC that locks the auction row and checks amount and deadline, with no update or delete on bids. - Payment checks are UI-only: creating a checkout session does not check on the server that the caller is the winner or that the auction is closed and unpaid; the webhook then trusts the auction ID in the session metadata.
- Webhook retries are not deduplicated by
event.id; a retry rewritespaidAt, but the payment notification still fires once. - Testing depth: 66 Jest tests cover services and mappers with mocked repositories; triggers, RLS, the webhook, the Edge Function and the server actions are untested, and there is no end-to-end browser harness yet.
- Service role: the Stripe webhook and the Edge Function bypass RLS with a server-only key. One server action that writes through it has no auth check of its own; today only the webhook route imports it.