781 lines
52 KiB
Markdown
781 lines
52 KiB
Markdown
# Bill Tracker — Changelog
|
||
|
||
## v0.18
|
||
|
||
### Branding
|
||
- Replaced the top-navbar dollar-sign placeholder and duplicate text/version brand stack with the selected `/img/logo.png` BillTracker logo.
|
||
- The logo now serves as the BillTracker brand in the top navigation while preserving the existing navbar height and route behavior.
|
||
- Login now uses the BillTracker logo, shows linked build/version information near the login actions, and uses the authentik icon for OIDC login.
|
||
- Admin Authentication Methods now uses subtle authentik branding in the OIDC toggle/configuration/test-login controls.
|
||
- Cropped transparent padding from the BillTracker logo asset so it renders larger and more readably in the unchanged-height navbar.
|
||
- Promoted the transparent `logo_cut.png` artwork to the served `/img/logo.png` asset and enlarged the login-page logo while keeping the login card layout compact.
|
||
- Login logo sizing now follows the login form width so the brand grows and shrinks with the sign-in column instead of rendering too small.
|
||
- Legacy `/login.html` now redirects to the modern React `/login` screen so the old static login page is no longer served by stale links.
|
||
- Vite now copies only modern React public assets from `client/public`, preventing legacy `public/*.html`, CSS, and JS files from being emitted into `dist`.
|
||
- No backend, auth, tracker, bills, categories, settings, status, admin, or navigation-link behavior was changed.
|
||
|
||
### Security
|
||
- **OIDC ID token signature verification** now uses `openid-client@5` for full cryptographic validation via JWKS: signature, issuer, audience, expiry, nonce, and `sub` presence — tokens without a valid signature are rejected
|
||
- **OIDC client cache** invalidation path added; cache is keyed by issuer/client/redirect so Admin panel credential changes pick up a fresh client
|
||
- OIDC-provisioned accounts (empty `password_hash`, `auth_provider='oidc'`) continue to be blocked from local password login
|
||
- Tokens, auth codes, and client secrets are never logged at any point in the OIDC flow
|
||
- Session cookies no longer become `Secure` solely because `NODE_ENV=production`; this preserves login on plain-HTTP Docker deployments while still supporting `COOKIE_SECURE=true`, `HTTPS=true`, and HTTPS reverse-proxy detection.
|
||
|
||
### Added
|
||
- **Docker startup volume repair**: runtime now starts through `docker-entrypoint.sh`, creates `/data/db` and `/data/backups`, fixes `/data` ownership for the non-root `bill` user, then drops privileges before launching Node. This prevents SQLite migrations from failing with `SQLITE_READONLY` on mounted volumes.
|
||
- **Docker startup migrations**: entrypoint now runs `scripts/migrate-db.js` as the non-root app user before starting the server, so required SQLite schema migrations and seeded defaults complete before the app listens for requests. Set `RUN_DB_MIGRATIONS=false` only for special maintenance runs.
|
||
- **Database writability preflight**: startup now checks that `DB_PATH` and its parent directory are writable before opening SQLite, producing a clearer error if a bind mount or volume is genuinely read-only.
|
||
- **Release notes Markdown rendering**: the release notes viewer now renders inline Markdown such as `**bold**`, backticked code, and HTTPS links instead of showing the raw markers.
|
||
- **authentik configuration testing**: Admin Authentication Methods now includes a live OIDC discovery test for the entered issuer/client/redirect settings and a direct authentik login test button once OIDC is enabled.
|
||
- **authentik setup guardrails**: the Admin form now fills the Redirect URI from the current app origin, offers a "Use Current" reset, and warns when the Issuer URL looks like an authorize/token/userinfo endpoint instead of the provider issuer.
|
||
- **authentik client auth method**: Admin OIDC settings now include an advanced `client_secret_basic` / `client_secret_post` token endpoint authentication method selector. The default remains `client_secret_basic`, matching the previous `openid-client` behavior.
|
||
- **Admin user role management**: Admin Users table now lets an admin promote another user to `admin` or demote an admin back to `user`, with protections against changing your own role or removing the last admin account.
|
||
- **Single-user mode recovery**: User Settings now shows a Login Mode section while single-user mode is active, allowing the default user to restore multi-user login without needing access to Admin routes.
|
||
- **Admin navigation parity**: Admin users now keep the normal app navigation and get an Admin link after Status; `/admin` uses the same top nav so admins can return to Tracker/Bills/Categories/Profile/Settings/Status without typing a URL. Backend `/admin` protection remains unchanged.
|
||
- **Admin-controlled auth method toggles** in Admin panel (Authentication Methods card):
|
||
- `local_login_enabled` — enable/disable local username/password login (default: enabled)
|
||
- `oidc_login_enabled` — enable/disable OIDC/authentik login (default: disabled)
|
||
- Database-backed authentik/OIDC provider settings: provider name, issuer URL, client ID, client secret, redirect URI, scopes, auto-provision, admin group, default role
|
||
- Lockout protection: admin cannot disable all login methods; cannot disable local login unless OIDC is configured, enabled, and has an admin group mapping
|
||
- Client secret is write-only in the UI/API; Admin GET returns only `oidc_client_secret_set`
|
||
- **`GET /api/admin/auth-mode`** extended: returns `local_login_enabled`, `oidc_login_enabled`, `oidc_configured`, `can_disable_local`, `warnings`, safe OIDC settings, and the client-secret marker
|
||
- **`PUT /api/admin/auth-mode`** extended: accepts all new provider settings, allows setting a new client secret, keeps the existing secret when blank, and supports explicit saved-secret clearing
|
||
- **`GET /api/auth/mode`** extended: returns `local_enabled`; returns OIDC provider name and login URL only when OIDC is enabled and fully configured
|
||
- **`POST /api/auth/login`** now checks `local_login_enabled` setting and returns 403 if admin has disabled local login
|
||
- **Login page OIDC button** uses `/api/auth/mode` so local-only, OIDC-only, and mixed login states are reflected safely
|
||
- **OIDC login and callback routes** now check both DB-backed effective OIDC config and `oidc_login_enabled` before proceeding
|
||
- Eleven auth settings keys are seeded: `local_login_enabled`, `oidc_login_enabled`, `oidc_provider_name`, `oidc_issuer_url`, `oidc_client_id`, `oidc_client_secret`, `oidc_redirect_uri`, `oidc_scopes`, `oidc_auto_provision`, `oidc_admin_group`, `oidc_default_role`
|
||
- **`scripts/test-oidc-smoke.js`** — 42 smoke tests covering PKCE, redirect sanitization, DB/env OIDC config precedence, safe secret handling, incomplete config behavior, provisioning, email linking, role/group mapping, OIDC-only local-login denial, and lockout logic; all pass
|
||
|
||
### Changed
|
||
- `services/oidcService.js` rewritten to use `openid-client@5` throughout:
|
||
- `getOidcClient(config)` — builds and caches an openid-client `Client` after OIDC discovery
|
||
- `buildAuthorizationUrl()` uses `client.authorizationUrl()` (uses discovered `authorization_endpoint`)
|
||
- `exchangeAndVerifyTokens()` replaces manual exchange + claims-only validation with `client.callback()` which does the full PKCE exchange, JWKS signature verification, and all claim checks in one call
|
||
- `getOidcConfig()` resolves provider config from DB settings first, env fallback second, safe defaults last
|
||
- `mapRoleFromClaims()` reads the effective admin group at runtime; default role remains `user`, and admin is granted only by explicit group match
|
||
- `findOrProvisionUser()` uses effective `oidc_auto_provision` and creates local OIDC users on first valid authentik login when enabled
|
||
|
||
### Notes
|
||
- Local username/password login remains supported and protected by lockout checks
|
||
- OIDC environment variables remain optional fallback/bootstrap values only; once a DB field is set, the DB value takes precedence
|
||
- Client secret is stored in the existing `settings` table, never returned through public endpoints, and never displayed in the UI
|
||
- Valid authentik users auto-provision local app users when enabled; unknown users receive a safe 403 when disabled
|
||
- Admin role is never granted by default; requires explicit OIDC admin group membership or local admin account
|
||
- OIDC signature verification/security is preserved through `openid-client@5` JWKS-backed validation, issuer/audience/expiry/nonce checks, PKCE, and state replay protection
|
||
- Live authentik SSO cannot be verified without a running authentik instance; all local testable code paths are covered by the smoke test script
|
||
- Admin single/multi user mode behavior was not changed
|
||
|
||
---
|
||
|
||
## v0.17
|
||
|
||
### Security
|
||
- **Rate limiting** added via `express-rate-limit`: login (10/15 min), password change (5/15 min), import (20/15 min), export (30/15 min), admin mutations (30/15 min), OIDC (20/15 min) — all per-IP, in-memory
|
||
- **Security headers** added globally: `X-Content-Type-Options: nosniff`, `X-Frame-Options: SAMEORIGIN`, `Referrer-Policy: strict-origin-when-cross-origin`, `X-Permitted-Cross-Domain-Policies: none`; `X-Powered-By` removed; `Strict-Transport-Security` added when `HTTPS=true`
|
||
- **CORS locked down**: `cors({ credentials: true })` (wide-open) replaced with opt-in via `CORS_ORIGIN` env var; without it, no CORS headers are sent and the browser's same-origin policy applies
|
||
- **Cookie `secure` flag**: session cookie now sets `secure: true` in production (`NODE_ENV=production` or `HTTPS=true`), preventing transmission over plain HTTP in deployed environments
|
||
- **Settings endpoint hardened**: `GET /api/settings` now returns only the four user-facing keys (`currency`, `date_format`, `grace_period_days`, `notify_days_before`); previously returned all settings rows including SMTP password hash and backup paths
|
||
- **DB path removed from status response**: `database.path`/`database.file` fields removed from `GET /api/status`; filesystem paths are no longer exposed to any authenticated user
|
||
- **OIDC-only account protection**: `login()` now rejects local-password login attempts for accounts provisioned via external OIDC, preventing bypass of SSO-only accounts
|
||
- **Error handler hardened**: global Express error handler logs `err.message` internally but no longer calls `console.error(err)` (which could log full stack traces with paths); user-facing errors remain useful but safe
|
||
- **CSP deferred**: Content-Security-Policy requires auditing inline styles from Tailwind and Radix event handlers; deferred to a dedicated hardening pass
|
||
|
||
### Added
|
||
- **Backend OIDC/authentik preparation** — disabled by default; activated only when `OIDC_ENABLED=true` plus all required env vars are present:
|
||
- `GET /api/auth/oidc/login` — generates PKCE code verifier + challenge, stores one-time state in `oidc_states` DB table, redirects to the identity provider's authorization endpoint
|
||
- `GET /api/auth/oidc/callback` — validates state, exchanges authorization code for tokens (PKCE), validates ID token claims (issuer, audience, expiry, nonce), maps to local user, creates session, redirects to frontend
|
||
- `GET /api/auth/mode` now includes `oidc_enabled`, `oidc_provider_name`, and `oidc_login_url` when OIDC is configured
|
||
- `services/oidcService.js`: OIDC discovery caching, PKCE helpers, login-state management, token exchange, claim validation, role/group mapping, user auto-provisioning
|
||
- `middleware/rateLimiter.js`: rate limiter factory for all endpoint categories
|
||
- `middleware/securityHeaders.js`: global security headers middleware
|
||
- `authService.createSession(userId)`: creates a server-side session for a user who has already been authenticated externally (used by OIDC callback)
|
||
- **Schema migrations** (v0.17, additive — safe on existing databases):
|
||
- `users.auth_provider TEXT DEFAULT 'local'` — tracks identity source (`local` | `oidc`)
|
||
- `users.external_subject TEXT` — stores OIDC `sub` claim for stable identity mapping
|
||
- `users.email TEXT` — stores email for OIDC-linked accounts and optional local-user email linking
|
||
- `users.last_login_at TEXT` — updated on every successful login (local or OIDC)
|
||
- `oidc_states` table: short-lived (5 min TTL) PKCE/nonce state for in-flight OIDC logins; pruned on each new login attempt
|
||
|
||
### Changed
|
||
- `authService.login()` now updates `last_login_at` on each successful local login
|
||
- `requireAuth` single-user-mode query now includes `display_name` so the top nav shows correctly in single-user mode
|
||
|
||
### Notes
|
||
- Local username/password authentication continues to work regardless of OIDC configuration
|
||
- OIDC requires `OIDC_ENABLED=true`, `OIDC_ISSUER_URL`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_REDIRECT_URI`
|
||
- JWT signature verification (JWKS) is not implemented in this pass — ID token *claims* are validated but the cryptographic signature is not. Install `openid-client@5` and upgrade `validateIdToken` in `services/oidcService.js` for full production-grade signature verification
|
||
- Admin full-database backup/restore was not changed
|
||
- No frontend SSO login button was added in this pass
|
||
|
||
---
|
||
|
||
## v0.16.2
|
||
|
||
### Added
|
||
- User SQLite data import backend for exports created by this app:
|
||
- `POST /api/import/user-db/preview`
|
||
- `POST /api/import/user-db/apply`
|
||
- SQLite import preview validates the uploaded file, confirms it is a BillTracker user data export, summarizes counts, warnings, and proposed create/skip/conflict actions, and writes no live data
|
||
- SQLite import apply uses the preview session, imports only into the signed-in user's account, creates missing user-owned records, skips duplicates/conflicts by default, and records import history
|
||
- Regression coverage for user SQLite preview, apply, conflict skipping, and invalid file rejection
|
||
|
||
### Changed
|
||
- Profile > My Data now shows Import Spreadsheet History and Import SQLite Data Export side by side on desktop and stacked on mobile
|
||
- Export My Data now appears below the two import tools
|
||
- The SQLite import UI includes file selection, preview summary, warnings/conflicts, confirmation before apply, and result/error states
|
||
|
||
### Security
|
||
- User SQLite import does not use admin backup/restore endpoints and does not perform a full system restore
|
||
- User SQLite import does not import users, password hashes, sessions, cookies, admin/global settings, SMTP credentials, backup files, server paths, or other users' data
|
||
- Uploaded SQLite files are read as data through parameterized queries, stored temporarily only for preview parsing, and cleaned up without exposing server paths
|
||
|
||
---
|
||
|
||
## v0.16.1
|
||
|
||
### Added
|
||
- Bills now support an optional `interest_rate` field for credit-card APR values; blank remains allowed for non-credit-card bills
|
||
- The bills database schema, API create/update routes, bill responses, and user exports now preserve `interest_rate`
|
||
- Bills page bill names now open the shared Edit Bill dialog directly
|
||
|
||
### Changed
|
||
- Edit Bill due-date editing now uses a recurring day-of-month number instead of a full calendar date picker
|
||
- Removed the old due-date wording from the shared Edit Bill UI; the field is now labeled "Due day of month"
|
||
- Bills page editing no longer relies on a separate Edit button as the primary action
|
||
- Bills page and Tracker both use the same shared Edit Bill dialog with the corrected due-day and APR fields
|
||
- Tracker due-date calculation now uses the recurring `due_day` field and clamps to shorter months using the existing month-end handling
|
||
|
||
### Notes
|
||
- The legacy `override_due_date` column remains in the database for compatibility, but the shared Edit Bill dialog no longer edits it and current tracker due-date calculation ignores it
|
||
- Payment, monthly state, bill delete, deactivate, reactivate, and global grace-period behavior were not changed
|
||
- Per-bill grace periods were not added
|
||
|
||
---
|
||
|
||
## v0.16
|
||
|
||
### Added
|
||
- Admin Cleanup / Maintenance panel in the Admin area with settings for all four cleanup tasks, last-run summary, Save Settings, and Run Cleanup Now button
|
||
- Import history trimming shows an explicit destructive-action warning when enabled
|
||
- Read-only Maintenance card on the user Status page showing last cleanup run timestamp and per-task result counts (import sessions pruned, temp files removed, backup partials removed); no admin controls exposed
|
||
- Tracker page: clicking a bill name opens the existing Edit Bill dialog for that bill; saving refreshes the tracker for the current month; monthly gear (⚙) still opens monthly state, not global bill edit
|
||
- Edit Bill dialog is now a shared component (`components/BillModal.jsx`); Bills page and Tracker page both import it — no duplicate dialog
|
||
|
||
### Changed
|
||
- `GET /api/auth/me` now returns `display_name` so the top-nav user menu always shows the current display name after Profile save without requiring logout/login
|
||
- `GET /api/status` now includes a safe read-only `cleanup` section: `last_run_at` and `last_result` task counts
|
||
- Edit Bill due-date helper text clarified that grace period is a global Setting
|
||
|
||
### Notes
|
||
- Per-bill grace period days are not a backend-supported field; grace period remains a global app setting in Settings
|
||
- Admin cleanup controls (Save Settings, Run Cleanup Now) are admin-only and do not appear on the user Status page
|
||
- Profile `display_name` save already merged correctly into local state; the auth session fix ensures `refresh()` also returns the updated value
|
||
|
||
---
|
||
|
||
## v0.15
|
||
|
||
### Added
|
||
- `services/cleanupService.js` — new cleanup service with four independent tasks:
|
||
- **Expired import sessions** — deletes `import_sessions` rows past their 24-hour expiry (previously only pruned on next preview request; now also runs daily)
|
||
- **Stale export temp files** — removes orphaned `bill-tracker-user-*.sqlite` files from the OS temp directory that were not deleted after an interrupted download; configurable max age (default 2 hours)
|
||
- **Orphaned backup partials** — removes `.partial` and `.upload` files left in the backup directory after a server crash; uses a fixed 2-hour safety cutoff so in-progress operations are never interrupted
|
||
- **Import history trimming** — optionally deletes `import_history` rows older than a configurable threshold; disabled by default
|
||
- Daily worker now runs all enabled cleanup tasks each morning after notifications; cleanup errors are caught and logged but do not fail the worker
|
||
- Admin cleanup API:
|
||
- `GET /api/admin/cleanup` — returns current cleanup settings and last run result
|
||
- `PUT /api/admin/cleanup` — update any combination of cleanup settings (partial updates supported)
|
||
- `POST /api/admin/cleanup/run` — trigger all enabled cleanup tasks immediately and return the result
|
||
- Eight new `cleanup_*` keys in the `settings` table with safe defaults (seeded via `INSERT OR IGNORE`)
|
||
|
||
### Notes
|
||
- No frontend admin UI for cleanup settings in this pass — backend and APIs only
|
||
- All cleanup tasks are independently toggled; disabling one does not affect others
|
||
- Import history trimming is off by default to preserve audit history; enable explicitly via `PUT /api/admin/cleanup`
|
||
- Backup partial pruning always uses a 2-hour minimum age regardless of settings so a live backup in progress is never removed
|
||
|
||
---
|
||
|
||
## v0.14.3
|
||
|
||
### Changed
|
||
- Added the shadcn Material Design theme registry setup and made Material Design the default light theme for the app
|
||
- `:root` now represents the Material Design light tokens used by the existing Tailwind/shadcn CSS variable system
|
||
- Aligned dark mode to the same Material-inspired design language so light and dark mode feel related
|
||
- Modernized shared theme surfaces and app shell styling, including the top navigation, cards, dialogs, dropdowns, buttons, inputs, badges, tables, and focus/hover/disabled states
|
||
|
||
### Notes
|
||
- The app still uses the existing Tailwind, shadcn, and Radix framework; no new UI framework was introduced
|
||
- Material Design is not an optional user-selected variant; it is the default light theme
|
||
- Backend behavior, routes, tracker, bills, payments, import/export logic, and database behavior were not changed
|
||
|
||
---
|
||
|
||
## v0.14.2
|
||
|
||
### Added
|
||
- Inactive bills now have a History Visibility editor on the Bills page
|
||
- History Visibility supports Default, Show all history, Show no history, and Show only selected date ranges
|
||
- Selected ranges mode supports adding, editing, deleting, labeling, and saving multiple year/month ranges
|
||
|
||
### Changed
|
||
- Bills with non-default history visibility or saved history ranges continue to show the history visibility indicator after saving
|
||
|
||
### Notes
|
||
- The editor is available for inactive bills only; active bills may show the indicator but do not expose the editor
|
||
- Delete, deactivate, and reactivate behavior was not changed
|
||
- No backend behavior changed
|
||
|
||
---
|
||
|
||
## v0.14.1
|
||
|
||
### Added
|
||
- Bills page now exposes permanent bill deletion behind a strong confirmation dialog
|
||
- Delete confirmation explains that payments, monthly history, notes, and history ranges are permanently deleted and cannot be undone
|
||
- Delete confirmation requires an explicit acknowledgement checkbox before the destructive action is enabled
|
||
- Bills with historical visibility metadata now show a small history visibility indicator in the Bills table
|
||
|
||
### Notes
|
||
- Deactivate/reactivate remains the safe non-destructive option and still uses the existing active/inactive update behavior
|
||
- The delete dialog offers Deactivate instead for active bills and Activate instead for inactive bills
|
||
- No backend delete, deactivate, reactivate, payment, monthly state, or history range behavior changed
|
||
|
||
---
|
||
|
||
## v0.14
|
||
|
||
### Added
|
||
- Bill hard-delete: `DELETE /api/bills/:id` now permanently removes the bill and all associated payments, monthly state, and history ranges — inactivation (`PUT` with `active: 0`) remains the safer non-destructive alternative
|
||
- Bill history visibility: bills now carry a `history_visibility` field (`default`, `all`, `ranges`, `none`) for future UI control over which historical data is shown for inactive bills
|
||
- `bill_history_ranges` table: per-bill, multi-range date records for fine-grained history visibility control
|
||
- `GET /api/bills/:id/history-ranges` — list all history ranges for a bill
|
||
- `POST /api/bills/:id/history-ranges` — add a date range (start year/month, optional end year/month, optional label)
|
||
- `PUT /api/bills/:id/history-ranges/:rangeId` — update a history range
|
||
- `DELETE /api/bills/:id/history-ranges/:rangeId` — remove a history range
|
||
- Bills list and detail responses now include `history_visibility` and `has_history_ranges` flag for future UI icon support
|
||
|
||
### Changed
|
||
- `DELETE /api/bills/:id` changed from soft-delete (set active=0) to hard-delete; clients that need deactivation should use `PUT /api/bills/:id` with `{ active: 0 }` (unchanged behavior)
|
||
- AP flag badge on the Bills page is now emerald/green and uses boolean coercion to prevent the SQLite integer `0` from rendering as a visible "0" next to the badge
|
||
|
||
### Notes
|
||
- No delete-confirmation UI added in this pass — backend only for the delete/history changes
|
||
- No history-visibility UI added — backend and data model only
|
||
- All history-range queries are scoped to the bill's owning user
|
||
- Deleting a bill cascades automatically via database foreign keys (`ON DELETE CASCADE`)
|
||
|
||
---
|
||
|
||
## v0.13.3
|
||
|
||
### Changed
|
||
- Moved authenticated navigation from a left-sidebar-first shell to a top-navigation-first app header
|
||
- Aligned authenticated pages under the new shared top-nav shell with consistent page width, padding, background, and card surface styling
|
||
- Modernized the shared app background, top nav active states, user menu, theme toggle placement, and mobile navigation menu
|
||
|
||
### Notes
|
||
- Profile remains the user/account hub, and user Data tools remain under Profile > My Data
|
||
- Settings remains focused on app-level preferences
|
||
- Admin/system controls remain separated from regular user Profile and continue to be shown only in the admin area
|
||
- No backend import/export, tracker, bill, category, or payment behavior changed
|
||
|
||
---
|
||
|
||
## v0.13.2
|
||
|
||
### Changed
|
||
- User-owned spreadsheet import, user data export, user data import placeholder, and import history tools now live under Profile > My Data
|
||
- Removed the top-level Data item from the regular sidebar; `/data` now redirects to `/profile` for backward-compatible deep links
|
||
- Settings is narrowed to app-level preferences: appearance, currency, date format, and billing grace period
|
||
|
||
### Notes
|
||
- Admin/system backup and restore controls remain separate from regular user Profile
|
||
- Status remains a standalone operational/system health page
|
||
|
||
---
|
||
|
||
## v0.13.1
|
||
|
||
### Added
|
||
- Profile page frontend at `/profile` with profile summary, display-name editing, notification preferences, password change, user-owned exports, and import history
|
||
- Sidebar signed-in user name now links to the Profile page
|
||
- Profile API helpers for profile details, profile settings, password changes, export metadata, and import history
|
||
- User export download buttons for SQLite and Excel exports from the Profile page
|
||
|
||
### Fixed
|
||
- Startup crash: `UPDATE categories SET user_id = ?` now removes orphaned NULL-owner categories whose names already exist for the target user before assigning ownership, preventing a `UNIQUE(user_id, name)` constraint violation on databases that had previously completed the v0.12 migration
|
||
|
||
### Notes
|
||
- Profile page uses user-owned export endpoints only and does not include admin backup controls or backup paths
|
||
- Password values are only kept in local form state for submission and are cleared after successful password change
|
||
|
||
---
|
||
|
||
## v0.13
|
||
|
||
### Added
|
||
- Profile backend foundation: `GET /api/profile` returns safe user data (id, username, display_name, role, created_at, updated_at, last_password_change_at, notification preferences, export links)
|
||
- `PATCH /api/profile` — updates `display_name` (only safe user-owned field)
|
||
- `GET /api/profile/settings` — returns user-owned notification preferences from the users table
|
||
- `PATCH /api/profile/settings` — updates user notification preferences (partial update; omitted fields are preserved)
|
||
- `POST /api/profile/change-password` — strict password change requiring `current_password`, `new_password`, and `confirm_new_password`; always verifies the current password regardless of account state; records `last_password_change_at` on success
|
||
- `GET /api/profile/exports` — returns metadata and links for user data export actions (SQLite and Excel)
|
||
- `GET /api/profile/import-history` — returns the signed-in user's import history (delegates to existing import service)
|
||
- `display_name` and `last_password_change_at` columns added to the users table via additive migration
|
||
|
||
### Changed
|
||
- `PUT /api/settings` no longer accepts backup-related keys (`backup_enabled`, `backup_frequency_days`, `backup_keep_count`, `backup_path`) from regular users; those settings are admin-only and remain manageable through the admin backup routes
|
||
|
||
### Security
|
||
- All `/api/profile/*` endpoints require authentication; user identity is always derived from the session — `user_id` is never accepted from the request body
|
||
- Profile responses never include password hashes, session tokens, SMTP credentials, backup paths, or other users' data
|
||
- `POST /api/profile/change-password` always requires the current password; no bypass path exists
|
||
|
||
### Notes
|
||
- No frontend Profile UI in this pass — backend APIs only
|
||
- Admin full-database backup/restore behavior was not changed
|
||
- Existing `POST /api/auth/change-password` preserved for the first-login forced-reset flow
|
||
|
||
---
|
||
|
||
## v0.12
|
||
|
||
### Added
|
||
- Bills and categories now have database ownership fields so imported and manually created bills belong to the signed-in user
|
||
- User data exports are now enabled for SQLite and Excel formats
|
||
- User exports include only the signed-in user's bills, payments, categories, monthly bill state, notes, and export metadata
|
||
|
||
### Fixed
|
||
- Bill, category, payment, tracker, spreadsheet import, and export routes now filter user-owned data instead of using global bill/category records
|
||
|
||
### Notes
|
||
- Existing unowned bills/categories are assigned to the first regular user during migration when one exists
|
||
- Admin full-system backup/export behavior was not changed
|
||
|
||
---
|
||
|
||
## v0.11.4
|
||
|
||
### Added
|
||
- Creating a new bill from an XLSX import row now also imports other paid months for the same detected bill name from the current preview
|
||
- Related paid months create monthly state and payment records on the newly created bill
|
||
|
||
### Notes
|
||
- Related-month import is limited to exact normalized bill-name matches in the reviewed XLSX preview
|
||
- Rows for other bill names remain skipped and are not imported automatically
|
||
|
||
---
|
||
|
||
## v0.11.3.2
|
||
|
||
### Fixed
|
||
- Login now updates the shared auth state before navigating, preventing the app from sitting on the login flow after successful sign-in
|
||
- First-login password change now sends the backend field name expected by the auth route
|
||
- Privacy acknowledgment refreshes auth state before continuing to the tracker
|
||
|
||
---
|
||
|
||
## v0.11.3.1
|
||
|
||
### Fixed
|
||
- XLSX import history and apply summary now record the number of rows skipped during review even though skipped rows are not sent as apply decisions
|
||
- Import review still applies only confirmed non-skipped rows, preserving the safer focused apply payload
|
||
|
||
### Tests
|
||
- Added regression coverage that omitted reviewed skips are counted in both apply results and import history
|
||
|
||
---
|
||
|
||
## v0.11.3
|
||
|
||
### Added
|
||
- XLSX import now detects `Paid Date` / `Date Paid` columns separately from due-date columns
|
||
- Confirmed XLSX imports now create payment records from detected paid dates and paid amounts so imported bills can appear paid in the tracker
|
||
- Import review rows now show and allow editing detected paid date and paid amount before Apply
|
||
|
||
### Notes
|
||
- Payment creation still only happens after the user confirms and applies the reviewed row
|
||
- Duplicate payment records with the same bill, amount, and paid date are skipped unless overwrite is enabled
|
||
|
||
---
|
||
|
||
## v0.11.2.5
|
||
|
||
### Fixed
|
||
- XLSX import apply now sends only rows the user chose to import instead of also sending every skipped preview row
|
||
- Import request parser failures on `/api/import/*` now return safe import-specific JSON errors instead of the generic `Internal server error`
|
||
|
||
### Notes
|
||
- Skipped preview rows remain visible and editable on the review screen, but they are not applied or created silently
|
||
- User confirmation is still required before creating a new bill, matching an existing bill, or applying any XLSX import row
|
||
|
||
---
|
||
|
||
## v0.11.2.4
|
||
|
||
### Fixed
|
||
- Fixed XLSX import month detection for real workbook tabs that combine month and year without a separator, such as `July2017`, `August2017`, and `September2017`
|
||
- Added tolerance for known month-name typos found in the test workbook, including `Januaru`, `Febuary`, and `Novevmber`
|
||
- All-sheets XLSX preview now skips obvious non-bill tabs such as `info`, tax/debt summary sheets, generic `Sheet13`-style tabs, and `home ownership expenses`
|
||
|
||
### Notes
|
||
- Electric rows from `Test_Data/monthly bills.xlsx` now resolve to the correct 2017 month values instead of becoming ambiguous because of tab-name parsing
|
||
- Import recommendations and apply remain confirmation-only; no auto-apply behavior was added
|
||
|
||
---
|
||
|
||
## v0.11.2.3
|
||
|
||
### Fixed
|
||
- Fixed remaining XLSX import apply error paths by normalizing and validating import decision fields before SQL writes
|
||
- Import apply now returns structured validation errors with row/field details when possible instead of generic Internal Server Error responses
|
||
- Import review now keeps the preview visible after apply failure so decisions can be corrected and retried
|
||
|
||
### Tests
|
||
- Added regression coverage for unsupported actions, unknown row IDs, missing create-new-bill names, invalid year/month values, bulk-style create-new-bill payloads without category IDs, frontend/backend match payloads, and skip rows without bill/month fields
|
||
|
||
---
|
||
|
||
## v0.11.2.2
|
||
|
||
### Fixed
|
||
- Fixed XLSX import apply validation for matching rows to existing bills
|
||
- Invalid match-existing-bill decisions now return clear validation errors instead of falling through to generic server errors
|
||
|
||
### Tests
|
||
- Added regression coverage for matched existing bill applies with numeric and string bill IDs, invalid match payloads, monthly state writes, bill template preservation, create-new-bill, and skip-row behavior
|
||
|
||
---
|
||
|
||
## v0.11.2.1
|
||
|
||
### Changed
|
||
- XLSX import bulk row tools are now visually scoped inside the XLSX Review Table, directly above the preview rows
|
||
- Bulk controls no longer use page-level sticky positioning, making it clearer they belong only to the current XLSX preview
|
||
|
||
### Notes
|
||
- Bulk actions still affect only selected XLSX preview rows and do not auto-apply imports
|
||
|
||
---
|
||
|
||
## v0.11.2
|
||
|
||
### Added
|
||
- Import review now supports bulk row selection and bulk row actions for the current XLSX preview
|
||
- Users can bulk mark selected preview rows as Skip
|
||
- Users can bulk mark selected preview rows as Create New Bill with editable prefilled name, category, due day, expected amount, actual amount, and notes when available
|
||
- Selected rows can be reset back to their recommendation/default decision
|
||
|
||
### Notes
|
||
- Bulk actions only update review decisions; no auto-apply or silent bill creation was added
|
||
- Per-row confirmation and editing remain available before Apply
|
||
- Rows without usable bill names remain unresolved after bulk Create New Bill until the user enters a name or skips the row
|
||
- Ambiguous rows are not auto-matched by bulk actions
|
||
|
||
---
|
||
|
||
## v0.11.1.1
|
||
|
||
### Changed
|
||
- Import review rows now let users override recommended matches and choose Create New Bill even when a possible existing bill match is suggested
|
||
- Create New Bill action switching now keeps the row expanded, hides the existing-bill selector, and sends a create-new-bill payload without the recommended bill ID
|
||
|
||
### Notes
|
||
- Recommendations remain confirmation-only and are not auto-applied
|
||
- Ambiguous rows still block Apply until the user chooses a valid action or skips the row
|
||
|
||
---
|
||
|
||
## v0.11.1
|
||
|
||
### Added
|
||
- XLSX import preview rows now include a `recommendation` object for pre-filling the review screen without applying changes automatically
|
||
- Smarter bill matching tolerates casing, punctuation, token order, and common abbreviations, including examples like `Capital` / `Cap One` → `Capital One` and `Discover Austin` → `Austin Discover`
|
||
- Create-new-bill recommendations now prefill likely bill name, due day, expected amount, and detected monthly amount when available
|
||
- Category suggestions use existing categories only, from explicit category columns, obvious keywords, or strongly matched similar bills
|
||
- Due-day suggestions are parsed from spreadsheet date/due-date cells when a valid day can be detected
|
||
- Amount recommendations carry detected spreadsheet amounts into confirmed monthly state decisions, and create-new-bill expected amounts when appropriate
|
||
|
||
### Changed
|
||
- Import review UI now shows the recommendation action, confidence, reason, and warnings before apply
|
||
- Apply payloads now include confirmed `category_id`, `due_day`, `expected_amount`, `actual_amount`, month/year, and notes when the user accepts or adjusts recommendations
|
||
- Weak or multiple possible bill matches remain unresolved until the user chooses a bill, creates a new bill, or skips the row
|
||
|
||
### Notes
|
||
- Recommendations are never auto-applied; the user must still review the preview screen and press Apply
|
||
- Ambiguous rows are not auto-applied and continue to block Apply until resolved
|
||
- Categories are not auto-created in this pass
|
||
- Existing bills are not silently updated, including due days or expected amounts; mismatches are shown as warnings
|
||
- Date intent is still heuristic: columns that look like payment dates lower confidence and warn rather than treating the date as a definite due date
|
||
|
||
---
|
||
|
||
## v0.11
|
||
|
||
### Added
|
||
- New dedicated **Data** page (`/data`) accessible from the sidebar with a FolderInput icon
|
||
- **Import Spreadsheet History** section: XLSX file picker with parse-all-sheets toggle, default year/month inputs, and a Preview Import workflow
|
||
- Preview UI shows workbook summary (sheet names, detected year/month, row counts, status badges), grouped by sheet tab in multi-sheet mode
|
||
- Per-row decision controls: auto-preselects high-confidence bill matches; ambiguous rows surface as "Needs decision" and block apply until resolved; user can choose match existing bill, create new bill, update monthly record, add monthly note, record as payment, or skip
|
||
- Suggested-match quick buttons and bill selector using optgroup (suggested matches / all bills) for fast selection
|
||
- Apply bar shows live summary of rows to apply, skipped, and unresolved; Apply button disabled until all rows are resolved
|
||
- Post-apply result card with created/updated/skipped/error counts; "New Import" button to start over
|
||
- **Download My Data** section (moved from Settings): SQLite and Excel export cards (Coming soon badges while backend endpoints are pending)
|
||
- **Import My Data Export** section: placeholder card explaining user-scoped SQLite import with Coming Soon state; clearly labelled as distinct from admin DB restore
|
||
- **Import History** section: table of all past imports for the current user with timestamps, file names, sheet names, and row outcome counts
|
||
- API helpers added: `api.previewSpreadsheetImport()`, `api.applySpreadsheetImport()`, `api.importHistory()`
|
||
|
||
### Changed
|
||
- Removed "Download My Data" section from Settings page — now lives exclusively on the Data page
|
||
- Settings page icon imports trimmed to only what the page uses
|
||
|
||
### Notes
|
||
- Admin full-database backup/restore remains exclusively in the Admin panel and is not accessible from the Data page
|
||
- User SQL import (user-scoped SQLite restore) shows a Coming Soon card; backend endpoints `POST /api/import/user-db/preview` and `POST /api/import/user-db/apply` are still needed
|
||
- User data exports (SQLite + Excel) remain Coming Soon; backend endpoints `GET /api/export/user-db` and `GET /api/export/user-excel` are still needed
|
||
|
||
---
|
||
|
||
## v0.10.1
|
||
|
||
### Added
|
||
- Multi-sheet preview mode: pass `?parse_all_sheets=true` to `POST /api/import/spreadsheet/preview` to parse every tab in one XLSX upload
|
||
- `parseSheetName()` — detects year/month from worksheet tab names supporting: `Jan 2026`, `January 2026`, `2026-01`, `01-2026`, `May`, `May Bills`, `Bills May 2026`, `2026 May`, and any combination of month name (full or abbreviated) with an optional 4-digit year
|
||
- Known non-data sheet names (Summary, Totals, Dashboard, Notes, Categories, Settings, Overview, Template, etc.) are automatically skipped in multi-sheet mode with `status: "skipped"` in the response
|
||
- `resolveYearMonth()` — tracks where each row's year/month came from; new `year_month_source` field on every preview row: `row_date`, `sheet_name`, `default`, or `ambiguous`
|
||
- Every row now includes `sheet_name` identifying which worksheet it came from
|
||
- Multi-sheet workbook response includes a `sheets` array with per-tab metadata: detected year, detected month, status (`parsed`, `parsed_month_only`, `ambiguous`, `skipped`), and row count
|
||
- Rows on ambiguous tabs (no detectable month) carry `year_month_source: "ambiguous"` and a warning; apply treats them normally using decision-level year/month override if provided
|
||
- `resolveYearMonth` and `parseSheetName` exported from service module for direct testing without DB
|
||
- Multi-sheet XLSX fixture (`scripts/test-import-multi-fixture.xlsx`) with Jan 2026, Feb 2026, Summary (skipped), May (month-only), and Misc Data (ambiguous) tabs
|
||
|
||
### Changed
|
||
- Single-sheet preview response now includes `parse_mode: "single_sheet"` in workbook metadata for consistency
|
||
- `parseXlsxBuffer()` now returns the full workbook object; raw-row extraction moved to `getSheetRows()`; `parseSheetRows()` extracted as reusable helper
|
||
- All preview rows now include `sheet_name` and `year_month_source` fields (single-sheet mode gets the selected sheet name and correct source)
|
||
- Row IDs in multi-sheet mode use `s{sheetIndex}_r{rowNumber}` format to guarantee uniqueness across tabs; single-sheet mode keeps existing `row_{N}` format
|
||
|
||
### Notes
|
||
- Apply behavior unchanged — `previewRow.detected_year` and `detected_month` already carried sheet-derived values through the session; no apply-path code changes needed
|
||
- Single-sheet preview behaviour is fully backward-compatible
|
||
- "1st–14th" style bucket names are treated as ambiguous (no month detected); provide `?month=N` default if needed
|
||
|
||
---
|
||
|
||
## v0.10
|
||
|
||
### Added
|
||
- XLSX spreadsheet import backend for importing historical bill data from Google Sheets exports (no direct Sheets API connection; user uploads an exported XLSX file)
|
||
- `POST /api/import/spreadsheet/preview` — parses an uploaded XLSX file safely, classifies rows, detects bill names/amounts/dates/labels, matches against existing bills, and returns a structured preview with proposed actions; writes no data
|
||
- `POST /api/import/spreadsheet/apply` — accepts confirmed import decisions from a preview session and applies only those decisions in a single transaction; ambiguous, conflicting, and skipped rows are never applied without explicit user confirmation
|
||
- `GET /api/import/history` — returns the authenticated user's import history (last 100 imports)
|
||
- Import session table (`import_sessions`) stores temporary preview state scoped to the uploading user; sessions expire after 24 hours and are cleaned up on next preview
|
||
- Import history table (`import_history`) records a per-user audit log of every apply: filename, sheet, row counts by outcome, and a decision summary
|
||
- Bill matching: exact normalized-name matches proposed automatically; partial/token-overlap matches require user confirmation; multiple matches mark row as requires_user_decision
|
||
- Duplicate detection for payments and monthly bill state; existing records are preserved by default unless `overwrite: true` is passed
|
||
- XLSX magic-bytes validation and formula parsing disabled (`cellFormula: false`) to prevent formula execution
|
||
- Test script (`scripts/test-import.js`) covering parseAmount, parseDate, detectLabels, normalizeName, row classification, XLSX round-trip, and fixture generation; also saves a test XLSX file for manual API testing
|
||
|
||
### Security
|
||
- Import endpoint requires authenticated user session; user_id is always taken from session, never from request body
|
||
- XLSX formula parsing disabled; all cells treated as plain string data
|
||
- File size limited to 10 MB; magic-bytes check rejects non-XLSX uploads
|
||
- Import sessions and history are scoped by user_id; sessions validate user ownership on load
|
||
- Temp/session data is not the original binary file; parsed row data is stored in the session table and cleaned up after apply or expiry
|
||
|
||
### Notes
|
||
- No frontend UI yet; this is the backend foundation for the import workflow
|
||
- No direct Google Sheets API integration in this pass; input is a user-exported XLSX file
|
||
- The `bills` table does not have a `user_id` column (shared household design); imported bills enter the shared pool, consistent with existing app behavior. Import history and sessions are per-user
|
||
- The `xlsx` (SheetJS) Community Edition has known prototype-pollution and ReDoS CVEs with no available OSS patch; mitigations applied (formula parsing off, size cap, magic-bytes check, authenticated-only access). Consider migrating to `exceljs` if stricter isolation is required
|
||
|
||
---
|
||
|
||
## v0.9
|
||
|
||
### Added
|
||
- "Download My Data" section in Settings with user-facing export cards for SQLite and Excel formats
|
||
- Export cards display "Coming soon" status pills and disabled buttons until backend endpoints are implemented
|
||
- "What's included" and "What's not included" info panels clarify that exports contain only the signed-in user's own data, not system backups
|
||
- Placeholder comments in `api.js` documenting the needed `GET /api/export/user-db` and `GET /api/export/user-excel` endpoints
|
||
|
||
---
|
||
|
||
## v0.8.2
|
||
|
||
### Fixed
|
||
- Docker runtime image now creates writable `/data/db`, `/data/backups`, and fallback `/app/backups` directories for the non-root app user
|
||
- Docker Compose now builds the project Dockerfile and mounts persistent storage at `/data` instead of bypassing the image setup with a plain Node image
|
||
- Docker Compose no longer requires a present `.env` file; explicit service environment defaults remain in the compose file
|
||
- Docker Compose now stores the SQLite database at `/data/db/bills.db`, matching the persistent `/data` volume and Dockerfile defaults
|
||
|
||
---
|
||
|
||
## v0.8.1
|
||
|
||
### Fixed
|
||
- Backup storage now respects `BACKUP_PATH` and otherwise derives a writable backup directory from the configured database path, preventing container permission errors from attempts to create `/app/backups`
|
||
|
||
---
|
||
|
||
## v0.8
|
||
|
||
### Added
|
||
- Admin backup management UI for creating, importing, listing, downloading, restoring, and deleting managed SQLite backups
|
||
- Admin scheduled-backup controls for enabling daily or weekly scheduled backups, choosing run time, setting scheduled-backup retention, saving settings, and running a scheduled backup immediately
|
||
- Scheduled backup worker using existing cron support; scheduled backups use managed `scheduled-backup-...sqlite` IDs and the same safe backup directory
|
||
- Admin backup settings endpoints for reading/saving schedule settings and running a scheduled backup on demand
|
||
|
||
### Changed
|
||
- Backup metadata now includes a backup type: manual, scheduled, imported, or pre-restore
|
||
- Backup status includes scheduled backup settings, next run, retention count, and last error when available
|
||
- Settings writes now update `updated_at` correctly so scheduled backup settings can be saved
|
||
|
||
### Security
|
||
- Backup delete is admin-only and only deletes managed backup IDs inside the controlled backup directory
|
||
- Scheduled retention only deletes old scheduled backups, never manual, imported, or pre-restore backups
|
||
|
||
---
|
||
|
||
## v0.7
|
||
|
||
### Added
|
||
- Admin-only `POST /api/admin/backups/import` endpoint for importing uploaded SQLite backup files into the managed backup directory
|
||
- Imported backups use server-generated `imported-backup-...sqlite` IDs, checksum metadata, and the same path validation as managed backups
|
||
|
||
### Security
|
||
- Uploaded backup imports are written to controlled temporary files, SQLite integrity-checked, and only promoted after validation succeeds
|
||
- Invalid or empty uploaded backup files are rejected without leaving temporary artifacts behind
|
||
|
||
---
|
||
|
||
## v0.6.1
|
||
|
||
### Security
|
||
- Backup status metadata now uses managed backup IDs instead of exposing filesystem paths
|
||
- Invalid SQLite backup files now fail restore validation with a safe HTTP 400 error
|
||
|
||
### Changed
|
||
- Backup status counts now use the backup service's managed backup list instead of scanning arbitrary files in the backup directory
|
||
|
||
---
|
||
|
||
## v0.6
|
||
|
||
### Added
|
||
- Admin-only SQLite database backup endpoints for creating, listing, downloading, and restoring backups
|
||
- Secure backup service with generated timestamped backup IDs, checksum metadata, SQLite integrity validation, and controlled backup directory handling
|
||
- Restore flow that creates a pre-restore safety backup before replacing the live database
|
||
|
||
### Security
|
||
- Backup download and restore IDs are strictly validated to prevent path traversal, absolute paths, nested paths, and arbitrary file access
|
||
- Backup API returns safe metadata only and does not expose backup directory paths
|
||
|
||
---
|
||
|
||
## v0.5
|
||
|
||
### Added
|
||
- `GET /api/version/history` returns the full `HISTORY.md` contents, current package version, and changelog last-modified timestamp
|
||
- Dedicated Release Notes page at `/release-notes` with loading, error, empty, and full-history display states
|
||
- Status page Release Notes card with current version, changelog last-updated timestamp, preview, and link to the full Release Notes page
|
||
- Frontend API helper `releaseHistory()` for fetching full release history
|
||
|
||
### Changed
|
||
- Status page release notes area now stays compact and links to the dedicated full-history view
|
||
|
||
---
|
||
|
||
## v0.4
|
||
|
||
### Added
|
||
- Status page operations cards for daily worker, notifications, backups, server clock, tracker health, and recent errors
|
||
- `/api/status` now returns backward-compatible operational status sections: `worker`, `notifications`, `backups`, `server`, `tracker`, and `recent_errors`
|
||
- Lightweight in-memory status runtime for worker state, notification test/error state, and recent backend errors
|
||
|
||
### Changed
|
||
- Quick Pay and inline payment creation on the Tracker page now scope new payments to the selected tracker month/year
|
||
- Status page runtime memory now reads `runtime.memory_mb` from the backend
|
||
- Status backend now includes database `last_modified`, server time, timezone, and current-month tracker counts
|
||
|
||
### Fixed
|
||
- Quick Pay no longer records payments against today's real month when viewing a previous or future tracker month
|
||
|
||
---
|
||
|
||
## v0.3
|
||
|
||
### Added
|
||
- `monthly_bill_state` table: stores per-bill, per-month overrides for actual_amount, notes, is_skipped
|
||
- `GET /api/bills/:id/monthly-state?year=&month=` — retrieve monthly state for a bill (returns defaults if no state set)
|
||
- `PUT /api/bills/:id/monthly-state` — upsert monthly state (actual_amount, notes, is_skipped) for a specific bill+month
|
||
- Tracker response now includes `actual_amount`, `monthly_notes`, and `is_skipped` fields per row from monthly_bill_state
|
||
- Export CSV now includes `Actual Amount` and `Monthly Notes` columns
|
||
|
||
### Changed
|
||
- `GET /api/tracker` rows now include monthly override fields when set; fall back to bill defaults when not set
|
||
- `GET /api/export` CSV output includes two new columns (backward-compatible, new columns appended)
|
||
|
||
---
|
||
|
||
## v0.2.1
|
||
|
||
### Fixed
|
||
|
||
- `dailyWorker.js`: Payment query for auto-draft status detection was missing `deleted_at IS NULL`, causing soft-deleted payments to count toward bill status in the daily worker
|
||
- `notificationService.js`: Payment query for notification suppression was missing `deleted_at IS NULL`, allowing a soft-deleted payment to incorrectly suppress due/overdue email notifications
|
||
- `payments.js GET /`: Year and month query parameters were interpolated directly into SQL string instead of using parameterized queries (SQL injection risk); replaced with bound parameters and proper validation
|
||
- `payments.js GET /`: End-of-month boundary was hardcoded as day 31 for all months; now computed using actual days-in-month per year/month
|
||
|
||
### Changed
|
||
|
||
- `payments.js POST /`, `POST /quick`, `POST /bulk`: Amount is now validated as a positive number (> 0); zero and negative amounts are rejected with HTTP 400
|
||
- `tracker.js GET /`: Year and month query parameters are now validated (year 2000–2100, month 1–12); invalid values return HTTP 400 instead of silently computing an invalid date range
|
||
- `payments.js GET /`: Year and month query parameters are now validated with the same rules; partial provision of only one is rejected with HTTP 400
|
||
- `export.js GET /`: Year query parameter is now validated (2000–2100); invalid values return HTTP 400
|
||
- DB schema: Added compound index `idx_payments_bill_date_del ON payments(bill_id, paid_date, deleted_at)` to accelerate the core tracker query pattern (`WHERE bill_id = ? AND paid_date BETWEEN ? AND ? AND deleted_at IS NULL`)
|
||
|
||
---
|
||
|
||
## v0.2
|
||
|
||
### Added
|
||
|
||
- `GET /api/version` — serves version and structured release notes from HISTORY.md
|
||
- **Paid Date column** in tracker table — shows payment date in green for paid bills
|
||
- `POST /api/payments/bulk` — batch payment recording in a single request
|
||
- Soft-delete for payments: `deleted_at` column + `POST /api/payments/:id/restore` endpoint
|
||
- `GET /api/tracker/upcoming?days=30` — upcoming bills feed sorted by due date
|
||
- `GET /api/bills/:id/payments?page=&limit=` — paginated payment history per bill
|
||
- `GET /api/export?year=YYYY&format=csv` — CSV export of all payments for a year
|
||
- Status page now displays current version and release notes from HISTORY.md
|
||
|
||
### Changed
|
||
|
||
- Payments schema: `deleted_at` column added (migration runs automatically on startup)
|
||
- `DELETE /api/payments/:id` now soft-deletes (sets `deleted_at`) instead of hard-deleting
|
||
- All payment queries now exclude soft-deleted records
|
||
|
||
---
|
||
|
||
## v0.1
|
||
|
||
### Added
|
||
|
||
- Initial release: React + Vite + Tailwind CSS + shadcn/ui frontend
|
||
- Three-theme system: Light, Dark, Dark Purple — persisted to localStorage
|
||
- Collapsible sidebar with Ctrl+B / ⌘+B keyboard shortcut
|
||
- Stripe-style layered layout with centered max-width container
|
||
- Full page set: Tracker, Bills, Categories, Settings, Status
|
||
- Admin panel with user management and onboarding wizard
|
||
- First-run terminal wizard + env-var non-interactive setup
|
||
- Single-user mode (bypass login for household use)
|
||
- Email notification system via SMTP (per-user or global recipient)
|
||
- Three-level auth: admin (user management only), user (full tracker access)
|
||
- First-login privacy notice informing users of admin limitations
|
||
- Docker deployment with persistent volume for DB and backups
|
||
- Legacy UI preserved at /legacy ("Remember When" mode)
|
||
- Release notes one-time dialog on version upgrade
|