main
CI / secrets-scan (push) Successful in 6s
CI / sast (push) Successful in 16s
CI / vuln-scan (push) Successful in 17s
CI / test (push) Successful in 27s
CI / lint (push) Successful in 33s
CI / build-images (push) Successful in 2m5s
CI / image-scan (push) Successful in 55s
CI / push (push) Successful in 37s
Reviewed-on: #2
TehRiehlBudget
A self-hosted personal finance application for tracking spending, balances, and net worth across all of your accounts — checking, savings, credit, loans, and investments — with manual transaction entry, receipt uploads, and a conversational AI advisor running entirely on your own hardware.
Deployed at budget.tehriehldeal.com.
Features
- Accounts — track checking, savings, cash, credit cards, loans, stocks, brokerage, and retirement accounts. Drag-to-reorder, per-account history, and a balance integrity check that reverses counter-party transfers when an account is deleted.
- Transactions — record income, expenses, and transfers between accounts. Filter by account, category, type, and date. Attach receipt images. Edit, delete, and bulk-export to CSV.
- Activity history — every create / update / delete is recorded with a snapshot, so deleted transactions and accounts remain auditable for as long as you need them. Filter by entity, action, account, or date range.
- CSV export — pull a date-ranged transaction set out as CSV from the global list or scoped to a single account, with quick-range presets (last 30/60/90 days, this year, all time) plus custom start/end pickers.
- Confirmation guards — every destructive action prompts before running, with light/dark mode parity.
- AI advisor — a "financial buddy" that summarizes your month against last month and answers follow-ups. Runs against a local Ollama model — no transaction data leaves your network.
- Dashboards — net worth and total debt at a glance, spending-by-category pie, monthly cash flow bar chart, and per-account balance history with overlaid valuations.
- Receipts — store receipt images on your own filesystem, never on a third-party blob store.
- Field-level encryption — sensitive columns (account numbers, transaction notes) are AES-256-GCM encrypted at rest.
Tech Stack
- Frontend — React 19 + TypeScript, Vite, TailwindCSS + ShadCN UI, Zustand, Recharts, React Router
- Backend — NestJS (TypeScript), REST, Prisma ORM
- Database — PostgreSQL (Dockerized for local dev)
- Auth — Supabase (email/password + OAuth, JWT-validated on the backend)
- AI — Ollama (self-hosted; defaults to
llama3) - File storage — local filesystem on the self-hosted server
- Package manager — pnpm workspaces
Project Structure
tehriehlbudget/
├── tehriehlbudget-frontend/ # React + Vite app
├── tehriehlbudget-backend/ # NestJS API + Prisma schema
├── docker-compose.yml # Postgres for local dev
├── pnpm-workspace.yaml
└── package.json # Workspace-level scripts
Prerequisites
- Node.js 20+
- pnpm 9+
- Docker (for local Postgres)
- A Supabase project (for auth) — free tier is fine
- An Ollama server reachable from the backend (local or LAN) for the AI advisor
Quick Start
# Clone and install
git clone <your-fork-url>
cd tehriehlbudget
pnpm install
# Start local Postgres
docker-compose up -d
# Configure the backend
cp tehriehlbudget-backend/.env.example tehriehlbudget-backend/.env
# Edit .env to set DATABASE_URL, SUPABASE_*, ENCRYPTION_KEY, OLLAMA_URL, OLLAMA_MODEL
# Apply database migrations
pnpm --filter tehriehlbudget-backend prisma migrate deploy
pnpm --filter tehriehlbudget-backend prisma generate
# Configure the frontend
cp tehriehlbudget-frontend/.env.example tehriehlbudget-frontend/.env
# Edit .env to set VITE_API_URL and VITE_SUPABASE_*
# Run both servers (in separate terminals)
pnpm dev:backend
pnpm dev:frontend
The frontend is served at http://localhost:5173 and the API at http://localhost:3000.
Required environment variables
Backend (tehriehlbudget-backend/.env)
| Variable | Description |
|---|---|
DATABASE_URL |
Postgres connection string |
SUPABASE_URL |
Your Supabase project URL |
SUPABASE_SERVICE_ROLE_KEY |
Service-role key (used to validate JWTs) |
ENCRYPTION_KEY |
32-byte hex key for AES-256-GCM field encryption |
OLLAMA_URL |
URL of your Ollama server (e.g. http://localhost:11434) |
OLLAMA_MODEL |
Ollama model id (e.g. llama3.2:latest) |
Frontend (tehriehlbudget-frontend/.env)
| Variable | Description |
|---|---|
VITE_API_URL |
Backend URL (e.g. http://localhost:3000) |
VITE_SUPABASE_URL |
Supabase project URL |
VITE_SUPABASE_ANON_KEY |
Supabase anon (publishable) key |
Common Commands
# Run dev servers
pnpm dev:frontend
pnpm dev:backend
# Build
pnpm build:frontend
pnpm build:backend
# Tests
pnpm test # both packages
pnpm test:backend # Jest
pnpm test:frontend # Vitest
pnpm test:coverage # both, with coverage
# Lint & format
pnpm lint
pnpm format
# Prisma
pnpm --filter tehriehlbudget-backend prisma migrate dev
pnpm --filter tehriehlbudget-backend prisma studio
Architecture Notes
- Field-level encryption. A NestJS interceptor encrypts sensitive columns on write and decrypts on read using AES-256-GCM. The plaintext only ever exists in memory inside the request handler.
- Receipt storage. Images are stored on the server's local filesystem. The backend issues access-controlled URLs for upload and retrieval — there is no S3 / external blob store.
- Balance integrity on delete. When an account is deleted, related transfers' counter-party balances are reversed inside the same Prisma
$transactionbefore the cascade fires, so a surviving account never reflects a transfer that no longer exists. - Audit log.
ActivityLogrows capture create / update / delete actions for transactions, accounts, and account valuations. For account deletions the per-transaction snapshots are written before the cascade so the trail outlives the data. - AI advisor. The advisor builds a per-request snapshot of standing balances, monthly flow numbers, and top spending categories, strips PII, and sends it to Ollama. The prompt explicitly separates point-in-time balances from monthly flow and instructs the model to never invent equations or derive new figures — your transaction data does not leave your network.
- Auth. Supabase issues JWTs; protected routes on both ends validate them. Sessions are persisted client-side via Supabase's SDK.
License
Languages
TypeScript
97.4%
CSS
1.1%
Dockerfile
0.7%
JavaScript
0.6%
HTML
0.1%