Skip to content
shift-primalPublic

About

wastescope

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

35 Commits

Folders and files

Repository files navigation

wastescope

Personal finance dashboard. Export your transactions from your bank, drop the file in, and wastescope categorizes everything and shows you where your money goes.

Parsing and categorization is done by txcategorizer.

Features

  • Import bank exports – upload a .csv/.txt export from a supported bank (currently DNB and Valle). The file is parsed in the browser, and you get a preview before anything is saved.
  • Automatic categorization – every transaction gets a category and a type (from txcategorizer).
  • Duplicate detection – rows you've already imported are flagged in the preview and skipped on insert, so re-importing overlapping exports is safe.
  • Category rules – re-categorize a transaction and every other transaction from the same merchant/counterparty follows. The choice is saved as a rule and applied to future imports too.
  • Dashboard – stat cards, spending per category, monthly chart, and a sortable transaction table (mobile list view on small screens).
  • Filters – date range, amount range, category, merchant and type. Filters can include or exclude values (long press on mobile to exclude).
  • Accounts – email/password auth with account settings, active sessions and a danger zone for wiping your data.
  • Demo accounts – five public demo logins with seeded data so people can try the app without signing up.
  • English and Norwegian, light and dark theme.

How it works

bank export (.csv/.txt)
        │
        ▼
txcategorizer.processTransactions()   ← runs in the browser
        │  parsed + categorized rows
        ▼
checkDuplicateTransactions (server fn) → preview with duplicates flagged
        │  user confirms
        ▼
importTransactions (server fn)
        │  apply the user's category rules, insert with ON CONFLICT DO NOTHING
        ▼
PostgreSQL ──► dashboard queries (stats, charts, table) via server fns + TanStack Query
  • All data access goes through TanStack Start server functions in src/server/functions/, guarded by an auth middleware that resolves the session and scopes every query to the current user.
  • Deduplication is enforced by a unique index on (user_id, date, amount, merchant, type, counterparty).
  • Category rules live in the category_rule table, keyed on (user_id, merchant, counterparty).
  • Demo accounts are read-only: importing, deleting, re-categorizing and account changes are blocked on the server (server functions plus a Better Auth before hook), not just hidden in the UI.

Stack

Area Used
Framework TanStack Start (React 19, SSR, server functions), TanStack Router
Data fetching / state TanStack Query, TanStack Form, TanStack Table
Database PostgreSQL 17, Drizzle ORM + drizzle-kit migrations
Auth Better Auth + Better Auth UI
UI Tailwind CSS v4, shadcn/ui on Base UI, Recharts, Phosphor icons
i18n Paraglide JS (en, no)
Validation Zod
Tooling Vite, React Compiler, Biome, Vitest, TypeScript, pnpm
Deploy Docker + Docker Compose (Nitro node server)

Project layout

src/
  routes/            file-based routes (dashboard, import, auth, settings)
  components/        dashboard, import, layout and shadcn components
  server/functions/  server functions (transactions, user)
  server/middleware/ auth middleware
  db/schema/         Drizzle tables (auth, transactions, category rules)
  db/queries/        query layer used by server functions
  lib/               auth config, hooks, formatting, schemas, demo account helpers
  content/           UI copy, wired to Paraglide messages
messages/            translation files (en.json, no.json)
drizzle/             generated SQL migrations
scripts/             seeding and maintenance scripts
demo_data/           sample bank exports used to seed demo accounts
tests/               Vitest tests

Running locally

Requires Node 22+, pnpm and Docker.

pnpm install

# start Postgres
pnpm docker:up

# configure env
cp .env.example .env

Fill in .env:

DATABASE_URL="postgresql://wastescope:wastescope@localhost:5432/wastescope"  # matches docker-compose.yml
BETTER_AUTH_URL=http://localhost:3000
BETTER_AUTH_SECRET=   # openssl rand -base64 32

Then:

pnpm db:migrate             # apply migrations
pnpm seed:demo-accounts     # optional: create the demo accounts
pnpm dev                    # http://localhost:3000

Useful scripts

Script What it does
pnpm dev Dev server on port 3000
pnpm build / pnpm preview Production build / preview it
pnpm test Run Vitest
pnpm check Biome lint + format check
pnpm typecheck tsc --noEmit
pnpm db:generate Generate a migration after changing the schema
pnpm db:studio Open Drizzle Studio
pnpm seed:transactions Seed fake transactions
pnpm seed:demo-reset Wipe and re-seed the demo accounts
pnpm db:clearall Clear the database

Deployment

docker-compose.prod.yml builds the app from the Dockerfile and runs it in order:

  1. db – Postgres
  2. migrate – runs pnpm db:migrate
  3. seed – wipes and re-seeds the demo accounts (runs on every deploy)
  4. app – the built Nitro server on port 3000

It needs POSTGRES_PASSWORD, BETTER_AUTH_SECRET and BETTER_AUTH_URL set in the environment.

docker compose -f docker-compose.prod.yml up -d --build

About

wastescope

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages