|
|
|
@ -2,7 +2,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
**Status:** Current code reference
|
|
|
|
**Status:** Current code reference
|
|
|
|
**Last Updated:** 2026-05-16
|
|
|
|
**Last Updated:** 2026-05-16
|
|
|
|
**Version:** 0.28.1
|
|
|
|
**Version:** 0.28.01
|
|
|
|
**Primary stack:** Node.js + Express, React + Vite, Tailwind CSS + shadcn/ui, Sonner, SQLite via `better-sqlite3`
|
|
|
|
**Primary stack:** Node.js + Express, React + Vite, Tailwind CSS + shadcn/ui, Sonner, SQLite via `better-sqlite3`
|
|
|
|
|
|
|
|
|
|
|
|
This manual reflects the current application code in `server.js`, `routes/`, `services/`, `middleware/`, `db/`, `client/`, `package.json`, `Dockerfile`, and `docker-compose.yml`. It is written as a current-state reference, not a changelog.
|
|
|
|
This manual reflects the current application code in `server.js`, `routes/`, `services/`, `middleware/`, `db/`, `client/`, `package.json`, `Dockerfile`, and `docker-compose.yml`. It is written as a current-state reference, not a changelog.
|
|
|
|
@ -496,23 +496,23 @@ Mounted under `/api/payments`; auth: user/admin tracker access. All queries are
|
|
|
|
- Response: live payment or 404.
|
|
|
|
- Response: live payment or 404.
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /payments`
|
|
|
|
- `POST /payments`
|
|
|
|
- Body: `{bill_id, amount, paid_date, method?, notes?}`.
|
|
|
|
- Body: `{bill_id, amount, paid_date, method?, notes?, payment_source?}`.
|
|
|
|
- Validation: bill exists and owned; amount > 0; required fields present.
|
|
|
|
- Validation: bill exists and owned; amount > 0; required fields present; payment_source one of `manual`, `file_import`, `provider_sync`, `transaction_match`.
|
|
|
|
- Response 201: created payment.
|
|
|
|
- Response 201: created payment.
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /payments/quick`
|
|
|
|
- `POST /payments/quick`
|
|
|
|
- Body: `{bill_id, amount?, paid_date?, method?, notes?}`.
|
|
|
|
- Body: `{bill_id, amount?, paid_date?, method?, notes?, payment_source?}`.
|
|
|
|
- Defaults amount to bill expected amount and date to today; confirms autodraft status for autopay bills.
|
|
|
|
- Defaults amount to bill expected amount and date to today; confirms autodraft status for autopay bills; defaults payment_source to `manual`.
|
|
|
|
- Response 201: created payment.
|
|
|
|
- Response 201: created payment.
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /payments/bulk`
|
|
|
|
- `POST /payments/bulk`
|
|
|
|
- Body: `{payments:[{bill_id, amount, paid_date, method?, notes?}]}`.
|
|
|
|
- Body: `{payments:[{bill_id, amount, paid_date, method?, notes?, payment_source?}]}`.
|
|
|
|
- Validation: array required; max 50; bill_id integer; `paid_date` `YYYY-MM-DD`; amount finite >= 0.
|
|
|
|
- Validation: array required; max 50; bill_id integer; `paid_date` `YYYY-MM-DD`; amount finite >= 0; payment_source must be valid.
|
|
|
|
- Duplicate live payments by user/bill/date/amount are skipped.
|
|
|
|
- Duplicate live payments by user/bill/date/amount are skipped.
|
|
|
|
- Response 201: `{created:[...], skipped:[...], errors:[...]}`.
|
|
|
|
- Response 201: `{created:[...], skipped:[...], errors:[...]}`.
|
|
|
|
|
|
|
|
|
|
|
|
- `PUT /payments/:id`
|
|
|
|
- `PUT /payments/:id`
|
|
|
|
- Body: partial `{amount, paid_date, method, notes}`.
|
|
|
|
- Body: partial `{amount, paid_date, method, notes, payment_source}`.
|
|
|
|
- Response: updated payment. Current code preserves existing fields when omitted.
|
|
|
|
- Response: updated payment. Current code preserves existing fields when omitted.
|
|
|
|
|
|
|
|
|
|
|
|
- `DELETE /payments/:id`
|
|
|
|
- `DELETE /payments/:id`
|
|
|
|
@ -581,6 +581,27 @@ Mounted under `/api/categories`; auth: user/admin tracker access.
|
|
|
|
- Validation: valid year/month; numeric amounts.
|
|
|
|
- Validation: valid year/month; numeric amounts.
|
|
|
|
- Response: recomputed starting-amount response after upsert.
|
|
|
|
- Response: recomputed starting-amount response after upsert.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.8.1 Snowball
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mounted under `/api/snowball`; auth: user/admin tracker access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /snowball`
|
|
|
|
|
|
|
|
- Response: current user's debt bills (snowball_include or debt-like categories), pre-sorted by snowball_order.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /snowball/settings`
|
|
|
|
|
|
|
|
- Response: `{extra_payment, ramsey_mode, ready_current_on_bills, ready_emergency_fund}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `PATCH /snowball/settings`
|
|
|
|
|
|
|
|
- Body: `{extra_payment?, ramsey_mode?, ready_current_on_bills?, ready_emergency_fund?}`.
|
|
|
|
|
|
|
|
- Response: saved settings and computed response.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /snowball/projection`
|
|
|
|
|
|
|
|
- Response: `{snowball, avalanche, minimum_only, comparison}` with enriched debt arrays including APR snapshots.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `PATCH /snowball/order`
|
|
|
|
|
|
|
|
- Body: `[{id, snowball_order}]`.
|
|
|
|
|
|
|
|
- Response: `{success:true}` after batch update.
|
|
|
|
|
|
|
|
|
|
|
|
### 5.9 Analytics
|
|
|
|
### 5.9 Analytics
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /analytics/summary?year=&month=&months=&category_id=&bill_id=&include_inactive=true&include_skipped=false`
|
|
|
|
- `GET /analytics/summary?year=&month=&months=&category_id=&bill_id=&include_inactive=true&include_skipped=false`
|
|
|
|
@ -703,7 +724,72 @@ Mounted under `/api/import`; auth: user/admin tracker access; import limiter app
|
|
|
|
- `GET /import/history`
|
|
|
|
- `GET /import/history`
|
|
|
|
- Response: current user's import history.
|
|
|
|
- Response: current user's import history.
|
|
|
|
|
|
|
|
|
|
|
|
### 5.15 Export
|
|
|
|
### 5.15 Data Sources
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mounted under `/api/data-sources`; auth: user/admin tracker access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /data-sources?type=&status=`
|
|
|
|
|
|
|
|
- Query params: `type` (`manual`, `file_import`, `provider_sync`), `status` (`active`, `inactive`, `error`).
|
|
|
|
|
|
|
|
- Response: array of data sources with `source_label`, `source_type_label`, `account_count`, `transaction_count`, safe fields (encrypted_secret excluded), timestamps, sync info.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.16 Transactions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mounted under `/api/transactions`; auth: user/admin tracker access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /transactions?limit=&offset=&match_status=&ignored=&source_type=&start_date=&end_date=&q=&data_source_id=&account_id=&matched_bill_id=`
|
|
|
|
|
|
|
|
- Response: paginated list of transactions with embedded data_source and account details, `source_label`, `source_type_label`.
|
|
|
|
|
|
|
|
- Filter: `match_status` (`unmatched`, `matched`, `ignored`), `ignored` (boolean), `source_type`, date range, free-text search (`q`) across description/payee/memo/category.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /transactions/manual`
|
|
|
|
|
|
|
|
- Body: `{account_id?, transaction_type?, posted_date, transacted_at?, amount, currency?, description?, payee?, memo?, category?, matched_bill_id?, match_status?, ignored?}`.
|
|
|
|
|
|
|
|
- Response 201: created transaction with manual `source_type`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `PUT /transactions/:id`
|
|
|
|
|
|
|
|
- Body: partial transaction fields.
|
|
|
|
|
|
|
|
- Validation: match_state changes use dedicated endpoints.
|
|
|
|
|
|
|
|
- Response: updated transaction.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `DELETE /transactions/:id`
|
|
|
|
|
|
|
|
- Behavior: soft-deletes via unmatch then hard delete.
|
|
|
|
|
|
|
|
- Response: `{success:true, deleted:true, id}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /transactions/:id/match`
|
|
|
|
|
|
|
|
- Body: `{billId}`.
|
|
|
|
|
|
|
|
- Response: `{success:true, matched:true, transaction}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /transactions/:id/unmatch`
|
|
|
|
|
|
|
|
- Response: `{success:true, unmatched:true, transaction}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /transactions/:id/ignore`
|
|
|
|
|
|
|
|
- Response: `{transaction}` with `match_status='ignored'`, `ignored=1`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /transactions/:id/unignore`
|
|
|
|
|
|
|
|
- Response: `{transaction}` restored to `unmatched` state.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.17 CSV Import
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mounted under `/api/import`; auth: user/admin tracker access; import limiter applies. Gated by `DATA_IMPORT_ENABLED` env var (defaults to true).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /import/csv/preview`
|
|
|
|
|
|
|
|
- Content-Type: `text/csv`.
|
|
|
|
|
|
|
|
- Body: raw CSV.
|
|
|
|
|
|
|
|
- Response: `{import_session_id, headers, sampleRows, rowCount, suggestedMapping, errors, fields}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /import/csv/commit`
|
|
|
|
|
|
|
|
- Body: `{import_session_id, mapping, options?}`.
|
|
|
|
|
|
|
|
- Response: `{imported, skipped, failed, details}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.18 Match Suggestions
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Mounted under `/api/matches`; auth: user/admin tracker access.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /matches/suggestions?transaction_id=&bill_id=&limit=&offset=`
|
|
|
|
|
|
|
|
- Response: `{suggestions:[{id, transaction, bill, score, match_status, created_at}]}`.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `POST /matches/:id/reject`
|
|
|
|
|
|
|
|
- Response: `{success:true}` after recording rejection.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 5.19 Export
|
|
|
|
|
|
|
|
|
|
|
|
Mounted under `/api/export`; auth: user/admin tracker access; export limiter applies.
|
|
|
|
Mounted under `/api/export`; auth: user/admin tracker access; export limiter applies.
|
|
|
|
|
|
|
|
|
|
|
|
@ -716,13 +802,13 @@ Mounted under `/api/export`; auth: user/admin tracker access; export limiter app
|
|
|
|
- `GET /export/user-db`
|
|
|
|
- `GET /export/user-db`
|
|
|
|
- Response: portable SQLite file with export metadata and user-owned categories, bills, payments, monthly state, monthly starting amounts, and notes.
|
|
|
|
- Response: portable SQLite file with export metadata and user-owned categories, bills, payments, monthly state, monthly starting amounts, and notes.
|
|
|
|
|
|
|
|
|
|
|
|
### 5.16 Status
|
|
|
|
### 5.20 Status
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /status`
|
|
|
|
- `GET /status`
|
|
|
|
- Auth: admin.
|
|
|
|
- Auth: admin.
|
|
|
|
- Response: app version, uptime, runtime worker state, DB health/counts/path/size, SMTP configuration status, backup status/schedule, current-month tracker health, recent errors.
|
|
|
|
- Response: app version, uptime, runtime worker state, DB health/counts/path/size, SMTP configuration status, backup status/schedule, current-month tracker health, recent errors.
|
|
|
|
|
|
|
|
|
|
|
|
### 5.17 About and Version
|
|
|
|
### 5.21 About and Version
|
|
|
|
|
|
|
|
|
|
|
|
- `GET /about`
|
|
|
|
- `GET /about`
|
|
|
|
- Public.
|
|
|
|
- Public.
|
|
|
|
@ -890,6 +976,130 @@ SQLite uses WAL mode and foreign keys. Base schema is in `db/schema.sql`; `db/da
|
|
|
|
- `created_at TEXT DEFAULT datetime('now')`
|
|
|
|
- `created_at TEXT DEFAULT datetime('now')`
|
|
|
|
- `updated_at TEXT DEFAULT datetime('now')`
|
|
|
|
- `updated_at TEXT DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `data_sources`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `type TEXT NOT NULL` (`manual`, `file_import`, `provider_sync`)
|
|
|
|
|
|
|
|
- `provider TEXT`
|
|
|
|
|
|
|
|
- `name TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `status TEXT NOT NULL DEFAULT 'active'` (`active`, `inactive`, `error`)
|
|
|
|
|
|
|
|
- `config_json TEXT`
|
|
|
|
|
|
|
|
- `encrypted_secret TEXT`
|
|
|
|
|
|
|
|
- `last_sync_at TEXT`
|
|
|
|
|
|
|
|
- `last_error TEXT`
|
|
|
|
|
|
|
|
- `created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- `updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- Unique partial index: `(user_id, type, provider)` WHERE `type='manual' AND provider='manual'`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `financial_accounts`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY`
|
|
|
|
|
|
|
|
- `user_id INTEGER REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `data_source_id INTEGER REFERENCES data_sources(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `provider_account_id TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `name TEXT`
|
|
|
|
|
|
|
|
- `org_name TEXT`
|
|
|
|
|
|
|
|
- `account_type TEXT`
|
|
|
|
|
|
|
|
- `currency TEXT`
|
|
|
|
|
|
|
|
- `balance REAL`
|
|
|
|
|
|
|
|
- `available_balance REAL`
|
|
|
|
|
|
|
|
- `raw_data TEXT`
|
|
|
|
|
|
|
|
- `created_at TEXT DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
- `updated_at TEXT DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
- Unique on `(data_source_id, provider_account_id)`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `transactions`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY`
|
|
|
|
|
|
|
|
- `user_id INTEGER REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `data_source_id INTEGER REFERENCES data_sources(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `account_id INTEGER REFERENCES financial_accounts(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `provider_transaction_id TEXT`
|
|
|
|
|
|
|
|
- `source_type TEXT NOT NULL` (`manual`, `file_import`, `provider_sync`)
|
|
|
|
|
|
|
|
- `transaction_type TEXT`
|
|
|
|
|
|
|
|
- `posted_date TEXT`
|
|
|
|
|
|
|
|
- `transacted_at TEXT`
|
|
|
|
|
|
|
|
- `amount INTEGER NOT NULL` (cents)
|
|
|
|
|
|
|
|
- `currency TEXT`
|
|
|
|
|
|
|
|
- `description TEXT`
|
|
|
|
|
|
|
|
- `payee TEXT`
|
|
|
|
|
|
|
|
- `memo TEXT`
|
|
|
|
|
|
|
|
- `category TEXT`
|
|
|
|
|
|
|
|
- `raw_data TEXT`
|
|
|
|
|
|
|
|
- `matched_bill_id INTEGER REFERENCES bills(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `match_status TEXT` (`unmatched`, `matched`, `ignored`)
|
|
|
|
|
|
|
|
- `ignored INTEGER NOT NULL DEFAULT 0`
|
|
|
|
|
|
|
|
- `created_at TEXT DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
- `updated_at TEXT DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
- Unique partial index on `(data_source_id, provider_transaction_id)` WHERE `provider_transaction_id IS NOT NULL`
|
|
|
|
|
|
|
|
- Indexes on `(user_id, COALESCE(posted_date, transacted_at, created_at))`, `(user_id, match_status, ignored)`, `account_id`, `matched_bill_id`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `import_sessions`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id TEXT PRIMARY KEY`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `created_at TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `expires_at TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `preview_json TEXT NOT NULL`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `import_history`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `imported_at TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `source_filename TEXT`
|
|
|
|
|
|
|
|
- `file_type TEXT DEFAULT 'csv_transactions'`
|
|
|
|
|
|
|
|
- `rows_parsed INTEGER DEFAULT 0`
|
|
|
|
|
|
|
|
- `rows_created INTEGER DEFAULT 0`
|
|
|
|
|
|
|
|
- `rows_updated INTEGER DEFAULT 0`
|
|
|
|
|
|
|
|
- `rows_skipped INTEGER DEFAULT 0`
|
|
|
|
|
|
|
|
- `rows_errored INTEGER DEFAULT 0`
|
|
|
|
|
|
|
|
- `options_json TEXT`
|
|
|
|
|
|
|
|
- `summary_json TEXT`
|
|
|
|
|
|
|
|
- `created_at TEXT DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `autopay_suggestion_dismissals`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `bill_id INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `year INTEGER NOT NULL CHECK(year BETWEEN 2000 AND 2100)`
|
|
|
|
|
|
|
|
- `month INTEGER NOT NULL CHECK(month BETWEEN 1 AND 12)`
|
|
|
|
|
|
|
|
- `dismissed_at TEXT NOT NULL DEFAULT (datetime('now'))`
|
|
|
|
|
|
|
|
- Unique: `(user_id, bill_id, year, month)`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `bill_templates`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `name TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `data TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `created_at TEXT DEFAULT (datetime('now'))`
|
|
|
|
|
|
|
|
- `updated_at TEXT DEFAULT (datetime('now'))`
|
|
|
|
|
|
|
|
- Unique: `(user_id, name)`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `match_suggestion_rejections`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `transaction_id INTEGER NOT NULL REFERENCES transactions(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `bill_id INTEGER NOT NULL REFERENCES bills(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `rejected_at TEXT NOT NULL DEFAULT (datetime('now'))`
|
|
|
|
|
|
|
|
- Unique: `(user_id, transaction_id, bill_id)`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `user_login_history`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `logged_in_at TEXT NOT NULL DEFAULT (datetime('now'))`
|
|
|
|
|
|
|
|
- `ip_address TEXT`
|
|
|
|
|
|
|
|
- `user_agent TEXT`
|
|
|
|
|
|
|
|
- `browser TEXT`
|
|
|
|
|
|
|
|
- `os TEXT`
|
|
|
|
|
|
|
|
- `device_type TEXT`
|
|
|
|
|
|
|
|
- `device_fingerprint TEXT`
|
|
|
|
|
|
|
|
|
|
|
|
#### `settings`
|
|
|
|
#### `settings`
|
|
|
|
|
|
|
|
|
|
|
|
- `key TEXT PRIMARY KEY`
|
|
|
|
- `key TEXT PRIMARY KEY`
|
|
|
|
@ -962,6 +1172,65 @@ Used for app settings, auth mode, OIDC settings, SMTP settings, backup schedule,
|
|
|
|
- `description TEXT NOT NULL`
|
|
|
|
- `description TEXT NOT NULL`
|
|
|
|
- `applied_at TEXT NOT NULL DEFAULT datetime('now')`
|
|
|
|
- `applied_at TEXT NOT NULL DEFAULT datetime('now')`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `data_sources`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `type TEXT NOT NULL` (`manual`, `file_import`, `provider_sync`)
|
|
|
|
|
|
|
|
- `provider TEXT`
|
|
|
|
|
|
|
|
- `name TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `status TEXT NOT NULL DEFAULT 'active'` (`active`, `inactive`, `error`)
|
|
|
|
|
|
|
|
- `config_json TEXT`
|
|
|
|
|
|
|
|
- `encrypted_secret TEXT`
|
|
|
|
|
|
|
|
- `last_sync_at TEXT`
|
|
|
|
|
|
|
|
- `last_error TEXT`
|
|
|
|
|
|
|
|
- `created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- `updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- Unique partial index: `(user_id, type, provider)` WHERE `type='manual' AND provider='manual'`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `financial_accounts`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `data_source_id INTEGER REFERENCES data_sources(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `provider_account_id TEXT`
|
|
|
|
|
|
|
|
- `name TEXT NOT NULL`
|
|
|
|
|
|
|
|
- `org_name TEXT`
|
|
|
|
|
|
|
|
- `account_type TEXT`
|
|
|
|
|
|
|
|
- `currency TEXT`
|
|
|
|
|
|
|
|
- `balance INTEGER`
|
|
|
|
|
|
|
|
- `available_balance INTEGER`
|
|
|
|
|
|
|
|
- `raw_data TEXT`
|
|
|
|
|
|
|
|
- `created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- `updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- Unique: `(data_source_id, provider_account_id)`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#### `transactions`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- `id INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
|
|
|
|
|
|
- `user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE`
|
|
|
|
|
|
|
|
- `data_source_id INTEGER REFERENCES data_sources(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `account_id INTEGER REFERENCES financial_accounts(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `provider_transaction_id TEXT`
|
|
|
|
|
|
|
|
- `source_type TEXT NOT NULL` (`manual`, `file_import`, `provider_sync`)
|
|
|
|
|
|
|
|
- `transaction_type TEXT`
|
|
|
|
|
|
|
|
- `posted_date TEXT`
|
|
|
|
|
|
|
|
- `transacted_at TEXT`
|
|
|
|
|
|
|
|
- `amount INTEGER NOT NULL`
|
|
|
|
|
|
|
|
- `currency TEXT`
|
|
|
|
|
|
|
|
- `description TEXT`
|
|
|
|
|
|
|
|
- `payee TEXT`
|
|
|
|
|
|
|
|
- `memo TEXT`
|
|
|
|
|
|
|
|
- `category TEXT`
|
|
|
|
|
|
|
|
- `raw_data TEXT`
|
|
|
|
|
|
|
|
- `matched_bill_id INTEGER REFERENCES bills(id) ON DELETE SET NULL`
|
|
|
|
|
|
|
|
- `match_status TEXT NOT NULL DEFAULT 'unmatched'` (`unmatched`, `matched`, `ignored`)
|
|
|
|
|
|
|
|
- `ignored INTEGER NOT NULL DEFAULT 0`
|
|
|
|
|
|
|
|
- `created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- `updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP`
|
|
|
|
|
|
|
|
- Unique partial index: `(data_source_id, provider_transaction_id)` WHERE `provider_transaction_id IS NOT NULL`
|
|
|
|
|
|
|
|
- Indexes: `(user_id, posted_date, transacted_at)`, `(user_id, match_status, ignored)`, `(account_id)`, `(matched_bill_id)`
|
|
|
|
|
|
|
|
|
|
|
|
### Indexes
|
|
|
|
### Indexes
|
|
|
|
|
|
|
|
|
|
|
|
Important indexes include:
|
|
|
|
Important indexes include:
|
|
|
|
@ -988,6 +1257,14 @@ Important indexes include:
|
|
|
|
- `idx_oidc_states_expires(expires_at)`
|
|
|
|
- `idx_oidc_states_expires(expires_at)`
|
|
|
|
- `idx_bill_history_ranges_bill(bill_id)`
|
|
|
|
- `idx_bill_history_ranges_bill(bill_id)`
|
|
|
|
- `idx_audit_log_user(user_id, created_at)`, `idx_audit_log_action(action, created_at)`
|
|
|
|
- `idx_audit_log_user(user_id, created_at)`, `idx_audit_log_action(action, created_at)`
|
|
|
|
|
|
|
|
- `idx_data_sources_user_type(user_id, type, status)`
|
|
|
|
|
|
|
|
- `idx_data_sources_user_manual(user_id, type, provider)` WHERE `type='manual' AND provider='manual'`
|
|
|
|
|
|
|
|
- `idx_financial_accounts_user_source(user_id, data_source_id)`
|
|
|
|
|
|
|
|
- `idx_transactions_user_date(user_id, posted_date, transacted_at)`
|
|
|
|
|
|
|
|
- `idx_transactions_user_match(user_id, match_status, ignored)`
|
|
|
|
|
|
|
|
- `idx_transactions_account(account_id)`
|
|
|
|
|
|
|
|
- `idx_transactions_matched_bill(matched_bill_id)`
|
|
|
|
|
|
|
|
- `idx_transactions_provider_dedupe(data_source_id, provider_transaction_id)` WHERE `provider_transaction_id IS NOT NULL`
|
|
|
|
|
|
|
|
|
|
|
|
### Migration system
|
|
|
|
### Migration system
|
|
|
|
|
|
|
|
|
|
|
|
@ -1023,6 +1300,22 @@ Current migration set:
|
|
|
|
- `v0.44` performance indexes.
|
|
|
|
- `v0.44` performance indexes.
|
|
|
|
- `v0.45` audit log.
|
|
|
|
- `v0.45` audit log.
|
|
|
|
- `v0.46` bill `cycle_type` and `cycle_day`.
|
|
|
|
- `v0.46` bill `cycle_type` and `cycle_day`.
|
|
|
|
|
|
|
|
- `v0.47` bills: `current_balance`, `minimum_payment`, `snowball_order`, `snowball_include`, `snowball_exempt`, `auto_mark_paid`, `deleted_at` columns.
|
|
|
|
|
|
|
|
- `v0.48` users: `snowball_extra_payment` column.
|
|
|
|
|
|
|
|
- `v0.49` payments: `balance_delta` column for debt payoff tracking.
|
|
|
|
|
|
|
|
- `v0.50` users: `last_seen_version` for release-notes notifications.
|
|
|
|
|
|
|
|
- `v0.51` user_login_history table.
|
|
|
|
|
|
|
|
- `v0.54` bills/categories: soft-delete columns (`deleted_at`).
|
|
|
|
|
|
|
|
- `v0.55` autopay: auto_mark_paid and suggestion dismissals table.
|
|
|
|
|
|
|
|
- `v0.56` bills: saved bill templates table.
|
|
|
|
|
|
|
|
- `v0.57` match_suggestion_rejections table.
|
|
|
|
|
|
|
|
- `v0.58` import_sessions and import_history tables.
|
|
|
|
|
|
|
|
- `v0.59` payments: `payment_source` and `transaction_id` columns.
|
|
|
|
|
|
|
|
- `v0.60` data_sources, financial_accounts, transactions tables.
|
|
|
|
|
|
|
|
- `v0.61` payments: one active payment per linked transaction (unique index on `transaction_id`).
|
|
|
|
|
|
|
|
- `v0.62` match_suggestion_rejections table.
|
|
|
|
|
|
|
|
- `v0.63` data_sources: partial unique index on `(user_id, type, provider)` WHERE type='manual' AND provider='manual'.
|
|
|
|
|
|
|
|
- `v0.64` transactions: partial unique index on `(data_source_id, provider_transaction_id)` WHERE provider_transaction_id IS NOT NULL; indexes on `(user_id, posted_date)`, `(user_id, match_status, ignored)`, `account_id`, `matched_bill_id`.
|
|
|
|
- Unversioned user notification columns are also reconciled.
|
|
|
|
- Unversioned user notification columns are also reconciled.
|
|
|
|
|
|
|
|
|
|
|
|
Migration logging is both console-based and audit-backed:
|
|
|
|
Migration logging is both console-based and audit-backed:
|
|
|
|
@ -1037,6 +1330,22 @@ Rollback support is defined by `ROLLBACK_SQL_MAP`:
|
|
|
|
- `v0.44` — drops selected performance indexes: `idx_bills_user_name`, `idx_payments_method`, `idx_monthly_starting_amounts_user`, and `idx_import_history_imported_at`.
|
|
|
|
- `v0.44` — drops selected performance indexes: `idx_bills_user_name`, `idx_payments_method`, `idx_monthly_starting_amounts_user`, and `idx_import_history_imported_at`.
|
|
|
|
- `v0.45` — drops `idx_audit_log_user`, `idx_audit_log_action`, and the `audit_log` table.
|
|
|
|
- `v0.45` — drops `idx_audit_log_user`, `idx_audit_log_action`, and the `audit_log` table.
|
|
|
|
- `v0.46` — drops `bills.cycle_day` and `bills.cycle_type`.
|
|
|
|
- `v0.46` — drops `bills.cycle_day` and `bills.cycle_type`.
|
|
|
|
|
|
|
|
- `v0.47` — drops `current_balance`, `minimum_payment`, `snowball_order`, `snowball_include`, `snowball_exempt`, `auto_mark_paid`, `deleted_at` columns from bills.
|
|
|
|
|
|
|
|
- `v0.48` — drops `snowball_extra_payment` column from users.
|
|
|
|
|
|
|
|
- `v0.49` — drops `balance_delta` column from payments.
|
|
|
|
|
|
|
|
- `v0.50` — drops `last_seen_version` column from users.
|
|
|
|
|
|
|
|
- `v0.51` — drops `user_login_history` table.
|
|
|
|
|
|
|
|
- `v0.54` — removes soft-delete columns (`deleted_at`) from bills and categories.
|
|
|
|
|
|
|
|
- `v0.55` — drops autopay suggestion dismissals table.
|
|
|
|
|
|
|
|
- `v0.56` — drops `bill_templates` table.
|
|
|
|
|
|
|
|
- `v0.57` — drops `match_suggestion_rejections` table.
|
|
|
|
|
|
|
|
- `v0.58` — drops `import_sessions` and `import_history` tables.
|
|
|
|
|
|
|
|
- `v0.59` — drops `payment_source` and `transaction_id` columns from payments.
|
|
|
|
|
|
|
|
- `v0.60` — drops `data_sources`, `financial_accounts`, and `transactions` tables.
|
|
|
|
|
|
|
|
- `v0.61` — drops unique index on `transaction_id` from payments.
|
|
|
|
|
|
|
|
- `v0.62` — drops `match_suggestion_rejections` table.
|
|
|
|
|
|
|
|
- `v0.63` — drops partial unique index on `data_sources`.
|
|
|
|
|
|
|
|
- `v0.64` — drops partial unique index and indexes on `transactions`.
|
|
|
|
|
|
|
|
|
|
|
|
`rollbackMigration(version)` requires an initialized database, verifies the version exists in `schema_migrations`, looks up rollback SQL in `ROLLBACK_SQL_MAP`, executes all rollback statements inside a transaction, deletes the migration record, logs elapsed time, audits success, and returns `{success:true, version, description, elapsed_ms}`. If the migration is not recorded, it throws `NOT_APPLIED`. If no rollback definition exists, it throws `ROLLBACK_NOT_SUPPORTED`. Execution failures roll back the transaction and are audited as `migration.rollback.failure`.
|
|
|
|
`rollbackMigration(version)` requires an initialized database, verifies the version exists in `schema_migrations`, looks up rollback SQL in `ROLLBACK_SQL_MAP`, executes all rollback statements inside a transaction, deletes the migration record, logs elapsed time, audits success, and returns `{success:true, version, description, elapsed_ms}`. If the migration is not recorded, it throws `NOT_APPLIED`. If no rollback definition exists, it throws `ROLLBACK_NOT_SUPPORTED`. Execution failures roll back the transaction and are audited as `migration.rollback.failure`.
|
|
|
|
|
|
|
|
|
|
|
|
@ -1178,7 +1487,7 @@ These use TanStack Query keys and cache server data for common pages.
|
|
|
|
|
|
|
|
|
|
|
|
### `package.json`
|
|
|
|
### `package.json`
|
|
|
|
|
|
|
|
|
|
|
|
Version: `0.23.2`.
|
|
|
|
Version: `0.28.1`.
|
|
|
|
|
|
|
|
|
|
|
|
Scripts:
|
|
|
|
Scripts:
|
|
|
|
|
|
|
|
|
|
|
|
|