Skip to content

sethvoltz/totornot

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

92 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Tot or Not

A side-by-side potato dish ranking app running at totornot.com. Two dishes enter, one dish leaves. Vote for your favorite and watch the global Elo rankings shift — or dial in your personal texture and flavor preferences to find your perfect spud match.

  • No accounts — just vote
  • Elo ranking — zero-sum, globally consistent scores
  • Spud Match — TOPSIS-based preference matching across 8 flavor/texture criteria
  • Anti-spam — hashed IP rate limiting (lazy token bucket)
  • Free to run — Cloudflare free tier (Workers + D1)

Tech Stack

Layer Technology
Framework SvelteKit 5 with Svelte runes
Styling Tailwind CSS v4
Runtime Cloudflare Workers
Database Cloudflare D1 (SQLite) via Drizzle ORM
Analytics PostHog
Ranking Elo rating system (K=32)
Matching TOPSIS multi-criteria decision analysis
i18n Paraglide JS
Components Storybook

Local Development

Prerequisites

1. Install dependencies

pnpm install

2. Set up your local environment

cp .env.example .env

Fill in your Cloudflare credentials in .env (used by drizzle-kit for migrations):

CLOUDFLARE_ACCOUNT_ID="your_account_id"
CLOUDFLARE_DATABASE_ID="your_database_id"
CLOUDFLARE_D1_TOKEN="your_token"

To get these: log into dash.cloudflare.com, create a D1 database, and generate an API token with D1 write permissions.

3. Generate TypeScript types

pnpm gen

This generates types from your Wrangler config and compiles Paraglide message files.

4. Apply migrations locally

pnpm dlx wrangler d1 migrations apply totornot --local

This sets up all tables and seeds the initial dish data. Migrations live in drizzle/migrations/ and are tracked in the d1_migrations table.

5. Start the dev server

pnpm dev

Open http://localhost:5173. Cloudflare bindings (D1, etc.) are emulated via getPlatformProxy. The local D1 state is stored in .wrangler/state/.

For testing the production build: pnpm preview — this runs the full Cloudflare Workers runtime locally.


Running Tests

# Unit tests
pnpm test:unit

# E2E tests (requires local D1 to be set up)
pnpm test:e2e

# Storybook component tests
pnpm test:storybook

# All tests
pnpm test

Project Structure

src/
  lib/
    criteria.ts           # 8 TOPSIS criteria definitions (crispiness, richness, etc.)
    elo.ts                # Elo calculation engine
    components/           # Shared Svelte components
    server/
      db/
        schema.ts         # Drizzle schema (dishes, votes, rate_limits, etc.)
        index.ts          # DB connection helper
      rateLimiter.ts      # Lazy token bucket rate limiter
      topsis.ts           # TOPSIS matching algorithm
      csrf.ts             # CSRF protection
      posthog.ts          # Server-side PostHog client
  routes/
    +page.svelte          # Voting matchup page (home)
    leaderboard/          # Elo leaderboard
    match/                # "My Spud Match" — TOPSIS preference finder
    about/                # About the project
    tip/                  # Dish suggestion tip line
    api/
      vote/               # POST /api/vote
      dishes/random/      # GET /api/dishes/random
      spud-match/         # POST /api/spud-match (TOPSIS)
      criteria-votes/     # POST /api/criteria-votes
      tip/                # POST /api/tip
    (internal)/
      rate-dishes/        # Internal criteria rating tool
drizzle/
  migrations/             # Wrangler-managed SQL migrations
wrangler.jsonc            # Cloudflare Workers config

Adding Dishes

Use the helper script to generate migration files:

# Create a new migration with one dish
bin/add-dish "Dish Name" "Description here"

# Append to the most recent migration instead of creating a new file
bin/add-dish "Another Dish" "Description" --append

# Apply the migration locally
pnpm dlx wrangler d1 migrations apply totornot --local

Flags: --append / -a, --help / -h


Schema Changes

  1. Edit src/lib/server/db/schema.ts
  2. Generate migration: pnpm dlx wrangler d1 migrations create totornot "describe_the_change"
  3. Write the SQL in the generated file under drizzle/migrations/
  4. Apply locally: pnpm dlx wrangler d1 migrations apply totornot --local
  5. Test, then apply remotely after deploy

Self-Hosting

First-time Cloudflare setup

# Log in
wrangler login

# Create the D1 database
wrangler d1 create totornot

# Paste the returned database_id into wrangler.jsonc, then run migrations
wrangler d1 migrations apply totornot --remote

# Set the IP hashing secret (generate with: openssl rand -base64 32)
wrangler secret put IP_HASH_SECRET

Deploy

pnpm build && wrangler deploy

Environment variables

Variable Where Description
IP_HASH_SECRET Wrangler secret HMAC key for hashing IPs — rate limiting disabled if unset
PUBLIC_POSTHOG_PROJECT_TOKEN wrangler.jsonc vars PostHog project token — analytics disabled if unset
PUBLIC_POSTHOG_HOST wrangler.jsonc vars PostHog ingest host
VOTE_RATE_LIMIT_PER_HOUR wrangler.jsonc vars Vote rate limit (default: 1000)
TIP_RATE_LIMIT_PER_HOUR wrangler.jsonc vars Tip submission rate limit (default: 1000)

Contributing

Pull requests are welcome. For significant changes, open an issue first.

About

Rank Potato Dishes for the best

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages