The Problem
Small community and charity events run silent auctions and tombolas on paper bid sheets and raffle tickets — slow, easy to dispute, and hard to promote sponsors through. The brief was a platform that a non-technical organiser could run end-to-end: bidder registration, live item bidding, a sponsor-video tombola, and winner notifications, without a server bill or a login system to maintain.
Google Sheets as the Database
The stack is the same free-tier pattern as the Incident Tracker project —
static HTML/JS pages, a Google Apps Script web app as the REST API, and
Google Sheets as the database, with nine tabs covering items, bids,
bidders, config, and the equivalent tombola tables. doGet() and
doPost() dispatch to action handlers, and a one-time setupSheets()
call creates the entire schema — headers, tabs, and seed config — so
standing up a new event doesn’t require touching a spreadsheet by hand.
Tombola Anti-Cheat: Tokens, Timing, and a Server-Side Lock
The tombola gives out raffle tickets for watching sponsor videos, which only works if a ticket can’t be self-awarded from the browser console. Watching a video issues a short-lived pending token; claiming a ticket checks that token server-side against elapsed time, not just its presence:
// Validate timing
const now = new Date();
const issued = new Date(issuedAt);
const elapsed = (now - issued) / 1000;
if (elapsed < 28) return { error: 'Video not completed — please watch the full ad' };
if (elapsed > tokenExpiry) return { error: 'Token expired — please watch a new ad' };
A 28-second floor against a ~30-second video means the client can’t skip
ahead and claim early, and an expiry window means a token can’t be held
and replayed later. Because two bidders could claim from the same prize
pool in the same second, the whole claim runs inside a LockService lock
so ticket counts can’t be double-spent under concurrent requests:
// Use LockService to prevent concurrent claims
const lock = LockService.getScriptLock();
try {
lock.tryLock(8000);
} catch(e) {
return { error: 'Server busy — please try again in a moment' };
}
try {
// ...validate token, bidder and prize, then award the ticket...
} finally {
lock.releaseLock();
}
Everything between the lock and its finally — token lookup, prize
capacity check, ticket write — runs as one atomic unit against the sheet.
Bidding and Registration
Registration issues a BID-XXXXXX bidder ID, with phone-based duplicate
detection so a returning bidder gets their existing ID back instead of a
second one, persisted client-side via localStorage. Each auction item
enforces its own minimum bid increment, tracks the current leader by name
and bidder ID, and a live countdown pulls the auction end time straight
from the Config sheet rather than being hardcoded per event.
The Admin Dashboard and WhatsApp Loop
The organiser’s dashboard covers items (with drag-and-drop image upload
through the ImgBB API), live bid tracking, and a winners view that’s
auto-generated on “Close All.” Every notification — auction winners,
tombola winners, sharing an item or the current top three — goes out as a
pre-filled WhatsApp deep link rather than email or SMS, which matches how
these events actually communicate on the day. Message formatting uses
WhatsApp’s own markdown (*bold*, _italic_) instead of emoji so it
renders consistently on both Android and Windows Chrome.
A Sponsor Proposal Generator
Beyond the auction itself, proposal-generator.html is a standalone tool:
fill in event and sponsor details, get a live-updating, print-ready
sponsorship proposal with cost-per-view calculated automatically from
estimated attendance — exported via the browser’s own Print → Save as PDF,
no PDF library required.
What This Project Demonstrates
- Server-side anti-cheat — timing validation and single-use tokens instead of trusting anything the client reports
LockServiceconcurrency control around a shared, limited resource (ticket counts) to prevent double-claims under simultaneous requests- A REST dispatcher pattern in Apps Script (
doGet/doPost→ action handlers) covering 22 endpoints across two related but independent systems — auction and tombola - Designing for the communication channel people actually use at these events — WhatsApp deep links, not email
- A second, fully separate tool (the proposal generator) built to solve an adjacent real-world problem — getting sponsors on board in the first place
Live: silentauction.morneydeetlefs.workers.dev GitHub: silentAuction
Next: Project 08 — KlasWerk. A full-stack learning management platform for trainers and students — course authoring, lesson delivery, quizzes and PayFast payments. In active development.