108 lines
4.0 KiB
Markdown
108 lines
4.0 KiB
Markdown
# App Spese
|
|
|
|
Full-stack personal finance app built with Next.js App Router, TypeScript,
|
|
Tailwind CSS, and PostgreSQL.
|
|
|
|
The browser only talks to Next.js Route Handlers. PostgreSQL credentials and
|
|
session tokens stay on the server.
|
|
|
|
## Docker Compose
|
|
|
|
Copy the example environment file and choose a strong database password:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
docker compose up --build -d
|
|
```
|
|
|
|
The first startup initializes the PostgreSQL schema from `database/init.sql`.
|
|
Open `http://localhost:3000`: the app redirects to `/setup` and asks you to
|
|
create the first account. The setup page becomes unavailable as soon as that
|
|
account exists.
|
|
|
|
## Local Development
|
|
|
|
Start PostgreSQL with Docker:
|
|
|
|
```bash
|
|
docker compose up -d postgres
|
|
```
|
|
|
|
The compose file publishes PostgreSQL on `localhost:5432` by default so the
|
|
local Next.js development server can reach it.
|
|
|
|
Create `.env.local`:
|
|
|
|
```env
|
|
DATABASE_URL=postgresql://appspese:change-me@localhost:5432/appspese
|
|
```
|
|
|
|
Install dependencies and run Next.js:
|
|
|
|
```bash
|
|
pnpm install
|
|
pnpm dev
|
|
```
|
|
|
|
## Data Model
|
|
|
|
Tables:
|
|
|
|
- `users` and `sessions`: local credentials and revocable login sessions.
|
|
- `subscriptions`: recurring payment rules.
|
|
- `subscription_payments`: paid or unpaid state for generated renewals.
|
|
- `bills`: expected manual bills with due dates.
|
|
- `one_time_payments`: immediate manual outgoing payments.
|
|
- `payments_ledger`: single source of truth for real paid outgoing money.
|
|
- `payment_titles`: reusable deduplicated payment names.
|
|
- `income`: incoming money.
|
|
- `user_settings`: one row of preferences per user.
|
|
|
|
Subscriptions and bills count as projected obligations until paid. Paid
|
|
outgoing money is written to `payments_ledger`, with unique database constraints
|
|
preventing duplicate ledger rows.
|
|
|
|
Subscriptions keep service activation and billing separate. Recurring payments
|
|
start from the first payment date, can stop at an optional end date or after an
|
|
optional number of installments, and may otherwise continue indefinitely.
|
|
Suspended and terminated subscriptions do not generate new virtual renewals,
|
|
while already recorded payments remain available as history.
|
|
Custom renewal intervals use an exact quantity and unit, supporting schedules
|
|
such as every two weeks, every three months, or every two years.
|
|
|
|
## Database Migrations
|
|
|
|
`database/init.sql` contains the complete schema for new installations.
|
|
Versioned files in `database/migrations` update existing volumes. After pulling
|
|
an update that includes a new migration, apply it once with:
|
|
|
|
```bash
|
|
cat database/migrations/001_subscription_lifecycle.sql | docker compose exec -T postgres sh -lc 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
cat database/migrations/002_subscription_custom_intervals.sql | docker compose exec -T postgres sh -lc 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
cat database/migrations/003_subscription_ledger_renewal_dates.sql | docker compose exec -T postgres sh -lc 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
cat database/migrations/004_income_titles.sql | docker compose exec -T postgres sh -lc 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
cat database/migrations/005_user_avatar.sql | docker compose exec -T postgres sh -lc 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
cat database/migrations/006_subscription_payment_snapshots.sql | docker compose exec -T postgres sh -lc 'psql -v ON_ERROR_STOP=1 -U "$POSTGRES_USER" -d "$POSTGRES_DB"'
|
|
```
|
|
|
|
## Authentication
|
|
|
|
There is no public registration page. The `/setup` page and its API endpoint are
|
|
available only while the database contains no users. The first request that
|
|
creates an account takes a PostgreSQL transaction lock, preventing concurrent
|
|
requests from creating multiple initial accounts.
|
|
|
|
- Passwords are hashed with bcrypt.
|
|
- Login sessions use random secrets stored in an HTTP-only cookie.
|
|
- Only a SHA-256 hash of each session secret is stored in PostgreSQL.
|
|
- API Route Handlers verify the current user server-side and ignore
|
|
client-provided user identifiers.
|
|
|
|
## Verification
|
|
|
|
```bash
|
|
pnpm lint
|
|
pnpm build
|
|
docker compose config
|
|
```
|