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.rsandbackend/src/handlers/budget.rs. - [MEDIUM/cognitive_load] Oversized file –
backend/tests/db_integration.rsat 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/workflowsusesdtolnay/rust-toolchain@stable(tag can move, risking secret exposure). - High – Commit a lockfile beside the manifest;
package.jsonhas no lockfile, so tested and shipped artifacts may differ. - Medium – Declare least‑privilege permissions for
GITHUB_TOKEN; two workflows have nopermissionsdeclaration. - 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-slimwithout digests. - Medium – Gate pull requests on a dependency vulnerability scan; CI lacks dependency‑review or
osv‑scanner. - Medium – Set
persist-credentials: falseon checkout;checkoutkeeps the token for later steps. - Medium – Add a non‑root
USERto the image; Dockerfile has noUSERdirective. - Low – Set
timeout-minuteson 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.