TehRiehlDeal a8a47e38c1
CI / secrets-scan (push) Successful in 8s
CI / sast (push) Successful in 13s
CI / vuln-scan (push) Successful in 15s
CI / test (push) Failing after 29s
CI / lint (push) Failing after 31s
CI / build-images (push) Has been skipped
CI / image-scan (push) Has been skipped
CI / push (push) Has been skipped
Add bulk statement-import feature for transactions
Lets a user upload a CSV, OFX/QFX, or PDF bank statement, parses the
transactions, flags duplicates and possible transfers against existing
records, and bulk-creates the accepted rows after a final read-only
confirmation step.

Backend
- New `statements` module with CSV (papaparse), OFX/QFX (node-ofx-parser),
  and PDF (pdf-parse) parsers behind a `StatementParser` strategy
  interface; format detected by content sniffing + extension.
- `DuplicateDetectorService` checks FITID/externalId exact matches first,
  then a date+amount+description Jaro-Winkler heuristic, then cross-account
  transfer pairing.
- New `POST /statements/parse` (multipart, in-memory, 10MB cap) returns the
  parsed preview without writing, including per-row `status` (`new`,
  `duplicate`, `needs_review`, `possible_transfer`) and any `needsMapping`
  payload when CSV headers are unrecognized.
- `POST /transactions/bulk` accepts up to 500 rows, chunks them 50 at a
  time inside `prisma.$transaction`, applies balance deltas, and writes a
  single `ActivityLog` row per chunk instead of one per transaction.
- Schema: nullable `external_id` column on `Transaction` plus composite
  indexes on `(account_id, external_id)` and `(account_id, date)` for fast
  dedupe-window queries. Not encrypted — it's an opaque bank ID used as a
  lookup key.

Frontend
- `ImportStatementDialog` runs a 4-step wizard: Upload → Column Mapping (if
  needed) → Review (editable table with duplicate/transfer badges) →
  Confirm (read-only summary with projected per-account balance impact and
  count-bearing primary button). The Confirm step gates the actual write,
  and Back to Review preserves all edit/checkbox state.
- New `bulkCreateTransactions` action on the transactions store.
- "Import Statement" button added next to Export on the Transactions page,
  with a success toast and a refresh of the transactions + accounts stores.

Tests
- 306 backend tests (29 suites), 195 frontend tests (31 suites), all green.
- Fixtures under `test/fixtures/statements/` cover three CSV sign
  conventions (signed-amount, debit/credit, credit-card), OFX 1.x SGML,
  OFX 2.x XML, and a credit-card QFX with the CCSTMTRS branch.

Versions bumped to 0.4.0 on both packages per the lockstep rule.

NOTE: the Prisma migration in
`prisma/migrations/20260527203542_add_transaction_external_id/` still
needs to be applied to the live database with `prisma migrate deploy` —
the DB at 10.0.3.82 wasn't reachable from the dev environment.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:23:45 -07:00
2026-04-29 22:23:04 -07:00
2026-05-04 16:20:23 -07:00
2026-05-04 16:20:23 -07:00
2026-05-04 16:20:23 -07:00
2026-05-04 16:20:23 -07:00

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 $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

S
Description
No description provided
Readme MIT 1.9 MiB
Languages
TypeScript 97.4%
CSS 1.1%
Dockerfile 0.7%
JavaScript 0.6%
HTML 0.1%