The Problem

Many individuals want a personal‑finance tracker that runs entirely on their own hardware, avoids third‑party services, and stays private. The README states the author “grew tired of my spreadsheet, and did not care for any of the third‑party services out there,” so this repo fills that niche with a minimal, self‑hosted application.

What This Does

payme consists of a Rust backend (backend/src/main.rs) compiled to a binary and a React frontend built with Vite (frontend/src/App.tsx). The backend uses SQLite (DATABASE_URL=sqlite:payme.db?mode=rwc) and exposes a JSON API; the frontend contacts frontend/src/api/client.ts (imported by 17 other modules) to render dashboards, settings, and budgeting UI. Docker Compose (docker-compose.yml) starts both services; the multi‑stage Dockerfile (Dockerfile) produces a thin image containing only the compiled binary and static assets. Database migrations run on startup via backend/src/db/mod.rs (create_pool, run_migrations).

How It Is Wired

Execution starts at backend/src/main.rs:11 (main), which calls create_app (backend/src/lib.rs) to wire routes, middleware, and services. The most‑called internal functions are auth_name and auth_value (each invoked from 64 call sites) and setup_with_user (59 call sites); these flow through backend/tests/common/mod.rs helpers (generate_token, create_test_user, etc.). The call graph contains 475 resolved edges; the hub module frontend/src/api/client is imported by 17 frontend components with no outward imports, giving it a blast radius of 17. Duplicated 6‑line blocks appear 239 times across 37 files, notably in backend/src/handlers/monthly_data.rs and backend/src/handlers/budget.rs. The oversized test file backend/tests/db_integration.rs (672 lines) concentrates integration logic, making changes ripple widely. Outside the repo, only the SQLite database is read/written.

How To Use It

Setup

# Clone the repository
git clone https://github.com/moses-y/payme.git
cd payme

# Backend (Rust)
cd backend
cargo build --release   # requires Rust 1.75+

# Frontend (Node)
cd ../frontend
npm install              # requires Node 20+

# Environment variables
cp .env.example .env     # edit DATABASE_URL, JWT_SECRET, PORT as needed

Running both services

chmod +x run.sh
./run.sh               # starts backend at http://localhost:3001 and frontend at http://localhost:3000

Docker (recommended)

echo "JWT_SECRET=$(openssl rand -base64 32)" > .env
docker compose up -d       # defined in docker-compose.yml
# Access at http://localhost:3001

Configuration Required env vars live in .env (derived from .env.example): DATABASE_URL, JWT_SECRET, PORT. The OpenAPI spec is at http://<ip>/swagger-ui.

Real‑World Use

A homelab admin runs docker compose up -d on a Raspberry Pi. Family members open the UI at http://pi-ip:3000, record income/expenses, and view budget summaries. When a tax season snapshot is needed, the UI’s “Download DB” button calls backend/src/handlers/export.rs (export_json) to produce a SQLite file that can be archived or imported into another tool.

Code Health & Issues

Static analysis (measured) identified 16 issues across 4 kinds:

  • [MEDIUM/clarity] Hub module x5 – frontend/src/api/client.ts, frontend/src/context/CurrencyContext.tsx, frontend/src/components/ui/Button.tsx; 17 modules depend on each, so changes have high‑blast‑radius impact.
  • [HIGH/cognitive_load] Deep nesting x9 – frontend/src/components/Stats.tsx, frontend/src/components/BudgetSection.tsx, frontend/src/components/CustomSavingsGoals.tsx; max indentation depth 8 makes control flow hard to follow.
  • [HIGH/clarity] Duplicated code blocks – 239 repeated 6‑line blocks across 37 files, chiefly in backend/src/handlers/monthly_data.rs and backend/src/handlers/budget.rs.
  • [MEDIUM/cognitive_load] Oversized file – backend/tests/db_integration.rs at 672 lines, difficult to hold in one mind; a change ripples widely.

SDLC observations (code‑health audit)

  • High – Pin third‑party GitHub Actions to a commit SHA; .github/workflows uses dtolnay/rust-toolchain@stable (tag can move, risking secret exposure).
  • High – Commit a lockfile beside the manifest; package.json has no lockfile, so tested and shipped artifacts may differ.
  • Medium – Declare least‑privilege permissions for GITHUB_TOKEN; two workflows have no permissions declaration.
  • Medium – Enable Dependabot or Renovate; three manifests exist but no update bot is configured.
  • Medium – Pin the container base image by digest; Dockerfile uses rustlang/rust:nightly-bookworm, node:26-bookworm, debian:bookworm-slim without digests.
  • Medium – Gate pull requests on a dependency vulnerability scan; CI lacks dependency‑review or osv‑scanner.
  • Medium – Set persist-credentials: false on checkout; checkout keeps the token for later steps.
  • Medium – Add a non‑root USER to the image; Dockerfile has no USER directive.
  • Low – Set timeout-minutes on workflow jobs; two jobs have no timeout.
  • Low – Add convention files this project lacks (.editorconfig, .gitattributes, formatter config).

The Bottom Line

payme delivers a lightweight, self‑hosted finance tracker with a clean Rust backend and React frontend, deployable via Docker Compose or direct cargo/npm runs. The codebase is functional but shows several maintainability concerns: duplicated logic, deep nesting, and a sizable integration test file that amplifies ripple effects. The hygiene audit flags critical CI and container security gaps (action pinning, lockfile, token handling) that should be addressed before production use. It is well‑suited for individuals or small teams comfortable with a DIY stack who value data privacy over feature breadth.