Skip to content

Repository files navigation

Ledger : personal savings and financial goals

A personal finance app built on Next.js 16 (App Router) and Supabase.

Its one organising idea: accounts hold money, goals reserve it. Assigning $200 to a goal does not create $200 — it marks money you already have as spoken for. The app keeps two separate ledgers so that distinction cannot be lost:

Ledger Table Answers
Account postings AccountPosting Where does my money physically sit?
Goal postings GoalPosting What is my money assigned toward?

Balances and goal progress are never stored. Both are sums over their ledger, computed in Postgres.


Setup

1. Install

npm install

2. Environment

cp .env.example .env.local

Fill in NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY from Supabase → Project Settings → API. Without them the app renders a setup screen instead of crashing.

The service-role key is not used anywhere in this codebase and must not be added.

3. Verify your schema before changing anything

Paste supabase/verify-schema.sql into the Supabase SQL editor and run it. It is read-only.

Query 1 matters most: it prints the enum labels verified against the linked live project. Compare the output against:

account_type        CHECKING, SAVINGS, CASH, BROKERAGE, OTHER
income_source_type  JOB, INTERNSHIP, FREELANCE, GIFT, OTHER
earning_status      PROJECTED, RECEIVED
transaction_type    INCOME, EXPENSE, ACCOUNT_TRANSFER, GOAL_CONTRIBUTION,
                    GOAL_WITHDRAWAL, GOAL_REALLOCATION, CORRECTION

If these differ, stop and reconcile the linked schema deliberately. Do not add enum labels merely to make the UI compile. Generated types stay in src/lib/db/database.types.ts; stable application aliases live in src/lib/db/schema-types.ts.

4. Apply the migrations

Apply these in order with supabase db push. They are re-runnable and do not drop, rename or rewrite user data. Each is explained in docs/BACKEND-ADDITIONS.md.

File What it adds Required?
0001_align_enums.sql Read-only assertion of the verified enum contract Yes
0002_archive_columns.sql archivedAt on FinancialAccount and Goal Yes
0003_views.sql AccountBalance, GoalProgress (security_invoker) Yes
0004_create_transaction.sql Atomic transaction + postings RPC Yes
0005_receive_earning.sql Idempotent "mark earning received" RPC Yes
0006_sync_goal_milestones.sql Milestone achievedAt stamping rule Yes
0007_indexes_and_constraints.sql Indexes + allocation/earning uniqueness constraints Recommended
0008_storage_avatars.sql avatars bucket + owner-only write policies For profile photos
0009_harden_rls.sql One strict ownership policy per user-owned table Yes

5. Regenerate types from your live schema

npx supabase login
npx supabase link --project-ref <your-project-ref>
npm run db:types

This overwrites src/lib/db/database.types.ts with the real schema, replacing the hand-authored version.

6. Run

npm run dev

Scripts

Command Does
npm run dev Development server
npm run build Production build
npm run typecheck tsc --noEmit
npm run lint ESLint
npm test Vitest
npm run assets Regenerate brand assets from Lucide
npm run db:types Regenerate DB types from the linked project

Deployment

Vercel

  1. Import the repository.
  2. Add NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY in Settings → Environment Variables (all environments).
  3. Deploy. Framework preset, build command and output are detected automatically.

Any Node host

npm ci && npm run build && npm start

Requires Node 20+. src/proxy.ts runs on the Node runtime, so a static export is not supported.

After deploying, in Supabase

Authentication → URL Configuration

  • Site URL — your production origin, e.g. https://your-app.vercel.app
  • Redirect URLs — add both:
    • https://your-app.vercel.app/auth/confirm
    • http://localhost:3000/auth/confirm (for local development)

Email confirmation and password-reset links will not work until these are set.

Authentication → Providers → Email — decide whether "Confirm email" is on. The app handles both: with it on, sign-up routes to a "check your email" screen; with it off, a session is created immediately and the user goes to onboarding. Nothing assumes a session exists after registration.


Documentation

Document Contents
docs/ARCHITECTURE.md Layers, data flow, the money pipeline, security model
docs/DESIGN.md Design research, the glass system, the validated chart palette
docs/BACKEND-ADDITIONS.md Every SQL object added, and why each one is necessary
docs/ASSUMPTIONS.md Assumptions made and features deliberately deferred
docs/QA-CHECKLIST.md Manual integration script, including cross-user isolation
docs/ASSETS.md The icon/asset system and its licence

Security summary

  • Row Level Security is the final authority. The app never disables it.
  • The service-role key is not read anywhere in this codebase.
  • Ownership always comes from the verified session (auth.uid() / requireSession()), never from a client-supplied id.
  • Every query and mutation re-establishes identity, because Next's proxy does not reliably cover Server Functions.
  • ?next= redirects are validated against open-redirect attacks.
  • Raw database errors are logged, never shown.

About

Personal finance app built on one idea: accounts hold money, goals reserve it. Two separate ledgers in Next.js and Supabase so the distinction cannot be lost.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages