# 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 ```bash # Clone and install git clone 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 ```bash # 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 `$transaction` _before_ the cascade fires, so a surviving account never reflects a transfer that no longer exists. - **Audit log.** `ActivityLog` rows 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 [MIT](./LICENSE)