OpenData Bridge
官民共存型オープンデータ管理システム。行政・民間が双方向にデータを持ち寄り、住所などの表記ゆれを自動で名寄せ・マージし、カタログと REST API で利活用できる基盤です。
Translation pending
Real-time sales tracker for the Cornell MBA Battle of the Brands: CLIF Builder's White Fudge Oreo.
DataGitHub @mbbadaroOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Repository information has not been collected yet.
No updates detected yet. GitHub releases will appear here.
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A mobile-first web app for the Cornell MBA Battle of the Brands. The team sells CLIF Builders White Fudge OREO flavor protein bars in three bundles, priced in tickets, from a shared stock of 200 bars. Several sellers record sales on their own phones at the same time, and everyone sees inventory and results update live.
| Product | Bars per package | Tickets |
|---|---|---|
| Single Bar | 1 | 3 |
| Week Pack | 3 | 6 |
| Exam Week Bundle | 5 | 8 |
Phones / laptops (Next.js 15 + React 19, TypeScript, Tailwind)
│ supabase-js with the signed-in user's JWT (anon key only; no secrets in the browser)
▼
Supabase
├─ Auth (email + password)
├─ PostgREST → RPC functions (all writes and reads go through them)
├─ Postgres: tables + row level security + SECURITY DEFINER functions
└─ Realtime: one "ticker" row; every write bumps it, every client refetches
record_sale, void_transaction, adjust_inventory, …). It checks the caller's role, validates input, recomputes bars and tickets from the product table, and checks stock. The browser only previews the numbers.event_settings row (SELECT … FOR UPDATE). Sales, voids and adjustments therefore run one at a time, and the stock check happens inside the lock. Tested with 60 concurrent sales.remaining = initial_inventory + Σ adjustments.delta − Σ bars of completed sales. Voiding a sale flips its status, so its bars come back automatically, and nothing can drift.client_request_id (UUID, unique in the table). Double taps and network retries reuse it, so the second call returns the first sale instead of creating another.live_ticker table is published to Supabase Realtime. It holds a version number, not sales, so NetIDs never go over the realtime feed. Clients refetch aggregates through RPCs when it changes, poll every 15 seconds as a fallback, and refresh when the tab regains focus or the network comes back.| Table | Purpose |
|---|---|
profiles | One row per team member: name, email, role (pending, seller, admin). Created by a trigger on sign-up. |
event_settings | Singleton: initial inventory (200), time zone, sales_started_at (locks initial inventory), duplicate-NetID window (120 s), seller undo window (5 min). |
products | The three products, with bars and tickets per package and an active flag. |
transactions | One row per sale: NetID, optional name and category, total bars and tickets, seller, status (completed/voided), void time, voider and reason, client_request_id. |
transaction_items | Quantity per product per sale, with the unit bars and tickets copied at sale time. |
inventory_adjustments | Restocks and corrections: signed delta, required reason, who and when. |
audit_log | Append-only record of sign-ups, role changes, inventory settings, adjustments, availability changes, voids and resets. |
live_ticker | Version counter for realtime refresh. |
Migrations: supabase/migrations/ (schema and RLS, functions, product and inventory configuration). supabase/setup.sql is all the migration files joined (npm run db:bundle), for pasting into the SQL editor.
pending and cannot see or do anything until an admin approves them in Admin → Team.anon has no table access and cannot call any function. Every function is SECURITY DEFINER with an empty search_path and checks the role itself.Requirements: Node 20.18 or newer (Node 22.18+ to run the database and browser tests) and a free Supabase project.
Create the Supabase project at supabase.com. Pick a region close to Ithaca (for example us-east-1).
Run the SQL. Either:
supabase/setup.sql and run it, ornpx supabase login, npx supabase init (keep the existing migrations), npx supabase link --project-ref <ref>, npx supabase db push.Auth settings (Authentication → Providers → Email): keep Email enabled. To let teammates sign up without waiting for a confirmation email, turn off Confirm email. That is safe here because new accounts stay pending until an admin approves them. If you leave it on, people click the email link first.
Realtime: nothing to do. The migration adds live_ticker to the supabase_realtime publication. (Check Database → Publications if your project uses a custom publication.)
Environment variables: copy .env.example to .env.local and fill in from Project Settings → API:
| Variable | Value | Secret? |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | https://<ref>.supabase.co | No |
NEXT_PUBLIC_SUPABASE_ANON_KEY | the anon / publishable key | No. It is designed to be public, and RLS plus the function checks are what protect the data. |
Never add the service_role key. The app doesn't use it.
npm install, then npm run dev and open http://localhost:3000.
Create the admin account first (the first sign-up becomes admin), then have each seller sign up and approve them.
Any Node host that runs npm run build && npm start works too (Netlify, Render, Railway, Fly).
Before the event
RESET ALL SALES. This deletes test sales and adjustments, keeps the team and the audit log, and unlocks initial inventory again.Recording a sale (Sell tab, about 5 seconds)
abc123; @cornell.edu is stripped automatically).During and after
All figures come from database records and exclude voided transactions.
| Command | What it checks |
|---|---|
npm test | Unit tests: sale formulas, NetID rules, analytics, highlights and ties, timeline, CSV escaping. |
npm run test:db | 26 database tests that run the real migrations on PostgreSQL (with a small stand-in for Supabase's auth schema): all 12 acceptance scenarios, 60 concurrent sales, idempotent retries, duplicate-NetID prompt, RLS (no direct writes, sellers can't see others' sales, anon blocked), restart persistence (PG_RESTART_CMD), reset. Needs a Postgres at PGHOST/PGPORT (default 127.0.0.1:5433). |
npm run test:e2e | 19 browser checks with Playwright: two separate devices (a phone and a laptop) using the production build against a test gateway (tests/e2e/mock-supabase.mjs) that speaks the Supabase Auth, RPC and Realtime protocols over the real migrated database. It covers every acceptance scenario, including the realtime sync between devices, the two-device race for the last 5 bars, and comparing the CSV download with the database. |
The test gateway exists only because a full local Supabase stack (Docker) wasn't available in the build environment. Against a real Supabase project the app talks to Supabase directly.