About

Scotty Fermo


RustFull-stackiOSAndroidPostgreSQLSystems

Zwipe is a solo project: designed, built, and shipped by one person. This page is the look under the hood, the architecture and the engineering discipline behind a one-person, full-stack Rust app. The goal was simple: make deck building feel good on mobile, and build it that way from the ground up.


System Architecture

Five Rust crates in one workspace. What each one does, and where it pulls from.

Mobile app

zwiper


iOSAndroidDioxusWASM

Website

zite



Backend

zerver


  • The REST API behind everything: auth, sessions, decks, cards, and users
  • Reads and writes a PostgreSQL database
  • A nightly job pulls the card catalog from Scryfall
  • Imports zwipe-core

Shared UI

zwipe-components


  • The Dioxus UI shared across the clients: buttons, action bar, card row, changelog
  • Reused beyond Zwipe on scottyfermo.com
  • Imports zwipe-core

Shared domain

zwipe-core


Pure Rust
  • Models, filter logic, and traits, with no server- or client-only dependencies
  • Imported by every other crate
  • Same domain code runs SQL filtering on the server and in-memory filtering on the device

Under the Hood

The engineering discipline behind it.

Design

Hexagonal architecture


Ports and adapters, in practice.

  • zwipe-core: zero framework dependencies
  • Inbound and outbound adapters swap freely
  • Same domain code: server SQL and on-device filtering
  • One codebase, compiled to iOS, Android, and web
Quality

Testing & lint discipline


694 tests, 406 in zwipe-core. Enforced by the compiler.

  • .unwrap, panic!, todo!, dbg!, and friends denied at compile time
  • 22 Clippy rules, workspace-wide
  • Compile-time SQL: sqlx query! fails the build, not runtime
  • Nightly Cloudflare R2 backups
Auth

Authentication


Hand-rolled, stricter than a deckbuilder needs.

  • Argon2id, with length, character-class and repetition rules
  • Rotating refresh tokens, replay-safe
  • Short-lived JWTs; refresh tokens stored hashed
  • Password newtype consumed on hash, so plaintext can't leak
  • Rate limiting, audit logs, transactional email
Types

Type safety


Newtypes everywhere. Invalid states don't compile.

  • UserId, Email, Password: real types, not String
  • Builders enforce required fields at construction
  • Formats as enums and traits, not bool flags
  • Validate once at the boundary, trust it downstream
Sync

Card data pipeline


110k+ printings nightly from Scryfall. The hard part isn't the cron.

  • Five-strategy upsert: batch, then per-row on conflict
  • ~327 cards per batch under Postgres's 65k-param cap
  • PartialEq delta detection: only changed rows written
  • Materialized view for dedup search (~35k unique)
  • Roles derived at sync, stored on the row: filter one indexed column (see card_role)