August 14, 2026

DanceTech — From Exam Tool to Community Operations

Check-in, Stripe Checkout, and YAML exams for a weekly West Coast Swing studio

rustaxumhtmxaskamastripepostgresredisdanceweb development
View on GitHub →

Dancexam started as a way to learn production web development in Rust while grading West Coast Swing progression tests. That origin story is in the 2024 write-up. DanceTech is the same stack grown into the operations layer for a weekly studio: accounts, check-in, Stripe Checkout, a proctor queue, and YAML-defined exams.

The app is live for the Greenville Westies. Source is on GitHub. The exam YAML, Stripe product metadata, and role model are meant to be reused by another studio — Greenville is the first deployment, not a one-off.

DanceTech Logo

What changed

Dancexam was an exam app. A community that meets every week also needs to know who showed up, who paid, and who is allowed to take which class. Those problems share a user model, so folding them into one service beat running a second tool.

DanceTech now covers:

  1. Accounts — email/password (Argon2) or Google OAuth, JWT cookies, sessions in Redis
  2. Check-in — a Redis shopping cart, Stripe hosted Checkout, products gated by roles and optional time windows
  3. Exams — YAML tests, a live queue, proctor scoring (including live grading), roles granted on a pass
  4. Admin — role edits, Stripe catalog refresh, CSV exports of users, exams, and current roles

Cards never touch this server. Stripe hosted Checkout collects payment; DanceTech creates the session, shows a receipt, and uses Stripe product metadata to decide who can buy what.

graph TD
    subgraph DanceTech [DanceTech]
        Auth[Auth email or Google OAuth]
        CheckIn[Check-in Stripe Checkout]
        Exams[Exams queue proctor results]
        Admin[Admin roles products CSV]
        Auth --> CheckIn
        Auth --> Exams
        Auth --> Admin
    end

The stack, still Rust

Axum, Askama, HTMX, PostgreSQL (sqlx), Redis, Stripe Checkout. UI is Tailwind plus Flowbite, vendored locally rather than pulled from a CDN. Same bones as Dancexam; the new pieces are Redis sessions, OAuth, and a payment path that stays off the app’s PCI surface.

Design bets that survived contact with production

One source of truth for URLs

Every path lives on ROUTES in src/app/router.rs. The Axum router, redirects, and Askama templates all read that struct (rts.login, ROUTES.check_in). Parameterized paths are methods on the same type (administer_exam, queue_query) so a rename cannot drift between Rust and HTML.

Shared strings live in structs, not literals

HTML ids that HTMX targets across templates follow the same idea. Each feature has an Ids struct and an IDS constant. Handlers and templates use those fields so a typo in hx-target or HX-Trigger fails at compile time instead of as a silent no-op:

pub struct Ids {
    pub test_form: &'static str,
    pub queue_container: &'static str,
    pub refresh_queue_event: &'static str,
    pub queue_reload_handler: &'static str,
}

pub const IDS: Ids = Ids {
    test_form: "search-tests-form",
    queue_container: "queue-container",
    refresh_queue_event: "refresh-queue",
    queue_reload_handler: "queue-reload-handler",
};

Auth on the router, roles on the handler

Routes are grouped into auth_required, check_auth, and no_auth. Login is not permission. Exam administration, roster search, and mutating someone else’s queue entry also require Admin or Proctor. That split keeps middleware boring and puts the interesting checks next to the mutation.

Catalog and tests are data

Products come from Stripe (show-on-dancetech, requires-roles, optional show windows). Exams are YAML under test_definitions/. A background actor refreshes the Stripe catalog so the request path does not wait on the API. Changing what is for sale, or what a leader test scores, does not require a code deploy.

Lessons from running it for a real community

The 2024 post was about learning Rust. This one is about what happens after the first production deploy:

  1. Payments belong at Stripe. Hosted Checkout is less custom UI and far less liability. Metadata on the product is enough to gate who can buy a class.
  2. YAML exams still work. Instructors can change a test without touching Rust. That was true in Dancexam and is still the right call.
  3. Compile-time strings pay off. HTMX is easy to break with a mistyped id. Putting those strings in structs is the cheapest way to keep the UI honest.
  4. One app beats two. Check-in and exams share users and roles. Splitting them would have duplicated auth, admin, and the deployment story.

The project continues to evolve. Demo mode is still incomplete; exam email-on-completion and a few queue niceties are not built. The useful part is already in weekly use.

If you want the original learning-Rust narrative, start with From Python to Rust — Building a Production Web Application. The running system is at greenvillewesties.com.