Commit Graph

82 Commits

Author SHA1 Message Date
ZProger
1de9ac272e feat: branded login screen in front of Basic Auth
The browser's native Basic Auth prompt can't be restyled — it's chrome UI,
not part of the page. Replace it with Aurum's own login screen: nginx keeps
auth_basic on /api/* (the actual protection, unchanged), but drops it from
the SPA shell (just HTML/JS/CSS, no data) so the app can render its own
branded form instead. On submit the app validates the credentials against a
real protected endpoint, then attaches the resulting Authorization header to
every request itself — sessionStorage-backed, so closing the tab signs out.
Instances with Basic Auth left off (the default) never see this screen.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
2026-08-30 03:05:01 +03:00
technosexual
76c5caa667 fix: actually reject zero-amount goal contributions (#4)
`GoalContributionCreate.amount` used `Field(ne=0)`, but pydantic has no
"not equal" constraint — the keyword was accepted as unknown schema
metadata and never validated anything. `POST /api/goals/{id}/contributions`
with `"amount": "0"` returned 201 and wrote a contribution that moves the
goal's progress by nothing, and the comment right above the field says zero
is pointless.

Replaced with a field_validator so the rule is enforced, deliberately not
with `gt=0`: negative contributions are intentional (money taken back out of
a goal), and the added tests pin both halves of that.

This also clears the `PydanticDeprecatedSince20: Using extra keyword
arguments on Field (Extra keys: 'ne')` warning that the test suite printed
on every run — that warning was the symptom.


Claude-Session: https://claude.ai/code/session_01UmuMDFrwdvQkce885VaaTh

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 02:43:11 +03:00
technosexual
949d2df37f fix: enforce transfer invariants when updating a transaction (#3)
The transfer rules — destination required, destination different from the
source account, no category, and no destination on a non-transfer — were
enforced by TransactionBase's validator on create, but the PATCH route only
re-checked category-vs-type.

So `PATCH /api/transactions/{id} {"type": "transfer"}` on an ordinary
expense committed a transfer row with transfer_account_id = NULL, and only
then failed serializing the response (TransactionRead inherits the same
validator). The caller got a 500 and would reasonably assume nothing was
saved — but from that point on every `GET /api/transactions` failed the same
validation, so the transactions list, the app's main screen, returned 500
permanently, with no way left in the UI to delete the offending row.

The rules now live in one place, transfer_rule_violation(), used by both the
create schema and the update route, so the two paths can't drift apart
again. The update path validates the row as it would look after the patch
rather than only the fields sent, since switching type alone invalidates
fields that were left untouched.

The frontend already sends a complete payload on edit (explicit nulls for
category_id / transfer_account_id) and does these same checks client-side,
so this only closes the hole for direct API calls — no UI change needed.


Claude-Session: https://claude.ai/code/session_01UmuMDFrwdvQkce885VaaTh

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 02:42:37 +03:00
ZProger
c95eb12d57 docs: add full REST API reference (DOCS.md)
Aurum already exposes the same REST API its frontend uses; this documents
every endpoint (accounts, categories, tags, transactions incl. bulk CSV
import, recurring, budgets, goals, assets/net worth, dashboard/cash-flow/
reports, insights/advice, settings, backup) with request/response shapes,
enforced business rules, and curl recipes, so the API can be published and
used to integrate other programs with Aurum. README links to it from the
top nav and a new API section.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
2026-08-30 02:28:22 +03:00
ZProger
13d5d4833a docs: sync README with CSV import, tags, subcategories, idle cash alert, theme toggle
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FfUH7LVvXrsBTyGiVFHmGL
2026-08-25 19:36:32 +03:00
ZProger
19f5cfa429 feat: import transactions from a CSV bank statement
Fourth of the four ideas picked from the Maybe/Sure feature audit, and the
last one queued up. A 3-step wizard (account -> column mapping -> preview)
at /transactions/import, parsed entirely client-side (new lib/csv.ts —
delimiter auto-detect, 5 date formats, thousands/decimal-separator-aware
amounts) and submitted through a new all-or-nothing POST /transactions/bulk
endpoint, mirroring backup restore's validate-everything-before-touching-
anything approach.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 02:44:30 +03:00
ZProger
94782ee073 feat: add free-form tags on transactions
Third of the four ideas picked from the Maybe/Sure feature audit. Tags are
orthogonal to categories (category = what kind of spend, tag = which
event/project) — many-to-many, created inline while typing in the
transaction form, filterable from the Transactions page.

Also adds tests/test_backup.py, the first test coverage the backup
export/import path has ever had, and fixes a MissingGreenlet bug it
surfaced in restore_backup's tag-wiring.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 02:38:04 +03:00
ZProger
09a0e383e0 feat: idle cash alert and category hierarchy (subcategories)
Two of the four ideas picked from the Maybe/Sure feature audit:

- Idle cash: a 5th proactive alert flags a depository account (checking/
  savings/cash) sitting at or above a configurable balance with no
  transaction touching it for a configurable number of days.
- Category hierarchy: categories can now have one level of subcategories
  (Groceries -> Alcohol). Dashboard/report aggregations roll a
  subcategory's spending into its parent's slice.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 02:27:46 +03:00
ZProger
79e4ebb06b fix: sort category lists alphabetically on the Categories page
Expense/income lists were rendered in server sort_order (chart palette
slot order), which looks arbitrary for browsing/managing categories.
Sorts by translated name instead, locale-aware — same approach
BudgetFormModal's category picker already uses. Display-only; sort_order
itself is untouched since the dashboard donut chart still relies on it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 23:56:54 +03:00
ZProger
2c6fcd628c feat: add 4 default income categories, localize category display names
Fresh installs (this app is published on GitHub) only got Salary,
Freelance, Investments, Gifts, Other Income by default — adds Business
Income, Rental Income, Benefits, and Item Sales, rounding out the 8-slot
categorical palette. Only affects new databases (seeding is a one-time
no-op once any category exists).

Also fixes CategoryList.tsx (added last commit) to run category names
through translateCategoryName() like every other category display in the
app, and registers the new defaults' translation keys.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 23:51:33 +03:00
ZProger
9f5ea00227 feat: add category management page (create/edit/delete)
The backend already had full category CRUD but nothing in the UI exposed
it — categories could only be viewed inside charts. Adds a Categories nav
item and page with icon/color pickers, grouped by expense/income, backed
by new create/update/delete mutations. Also expands icons.tsx's ICON_MAP,
which was missing 14 icon keys already in use by real category data and
silently falling back to a wallet glyph everywhere they render.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 23:43:06 +03:00
ZProger
6cc532378b feat: auto-capitalize the first letter of transaction descriptions
Transactions/recurring templates entered with mixed casing ("траты на
продукты") are now capitalized on create/update via a Pydantic validator,
so the fix applies to every client, not just the web UI. Adds a one-off
backfill script for existing rows.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-24 23:10:22 +03:00
ZProger
2b2e01a8e2 feat: add search to Transactions tab, spanning all history
Lets the user find a past purchase by keyword instead of paging through
months manually — search hits description/merchant/notes and ignores the
month filter, with a "jump to month" action on each result to view it in
its normal monthly context.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 23:00:44 +03:00
ZProger
b9ff3ab404 fix: Transactions открывался на январе вместо текущего месяца
parseMonthParam передавал Number(null) дальше — а это 0, не NaN, поэтому
проверка "отсутствует/невалиден -> fallback" не срабатывала и месяц
зажимался в 1. Добавил явную проверку value === null и диапазон 1-12
вместо клампинга (тот же фикс для parseYearParam для единообразия).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 02:39:17 +03:00
ZProger
bbbd67bc72 fix: risk_level актива не сохранялся при создании и не отдавался в API
Asset(...) в create_asset игнорировал payload.risk_level, а _to_read()
не передавал risk_level в AssetRead — из-за дефолта в схеме API всегда
отвечал Medium вне зависимости от выбора в форме и данных в БД.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-17 02:14:09 +03:00
ZProger
d1458eb160 feat: view and edit a transaction's note from the list
Transactions can carry a `notes` field (set via the API — e.g. the
currency-conversion metadata on this user's imported historical data), but
there was no way to see it in the UI at all.

TransactionsTable now shows a note icon next to edit/delete, only when a
transaction actually has one, opening a dialog with the text. The edit
form's `notes` field was already wired end to end (state, payload) but had
no input in the JSX — added it, since a view-only note with no way to edit
or clear it would be a dead end.

Also fixed e2e/run.sh's readiness check, found while running the full
suite for this change: it polled `/` (nginx, answers almost immediately)
instead of `/api/health`, so the first test could start before the backend
finished migrating/seeding and fail trying to parse nginx's HTML error
page as JSON.
2026-08-16 23:50:10 +03:00
ZProger
7235d74f80 fix: move category spending chart above the ranking list on Reports
Picking a category at the top meant scrolling past the whole "top
categories" list before reaching its chart, then scrolling back up to
pick another one. Swapped the two so the chart renders immediately after
the category/range selector.
2026-08-16 23:37:51 +03:00
ZProger
1f5fab5010 refactor: merge Language and Theme settings into one two-column card
Language and Theme were two separate Cards stacked on top of each other —
both are pure client-side display preferences, so splitting them side by
side (language left, theme right) saves a card's worth of vertical space.
Collapses back to a single column on mobile.
2026-08-16 23:27:55 +03:00
ZProger
75dd43c8e3 feat: manual theme picker (light/dark/system) in Settings
Theme previously followed only the OS's prefers-color-scheme, which
switches automatically on this user's Mac at sunset/sunrise. Adds an
explicit override in Settings: Light/Dark/System, System being the
previous (default) auto-follow-the-OS behavior.

Persisted client-side in localStorage (lib/theme.ts, same pattern as
language) rather than server-side like currency — it's a pure display
preference, and synchronous localStorage reads are what make the
anti-FOUC inline script in index.html possible. index.css already had the
[data-theme] CSS variables laid in ahead of time; only the JS to actually
set the attribute, and the Settings UI, were missing.

Covered by 2 new Playwright specs (theme-switcher.spec.ts) run against the
isolated e2e stack: explicit choice overrides a dark OS and survives
reload; switching back to "system" doesn't leave a stale forced theme.
2026-08-16 23:12:20 +03:00
ZProger
8e46726b4d test: real backend pytest + E2E Playwright suites, both isolated from prod data
backend/tests/: 33 pytest tests against a real Postgres database
(aurum_test, created/migrated/dropped per run) exercised through the actual
HTTP API — not sqlite, not mocks, and the real `aurum` database is never
opened (app lifespan is skipped, get_session is overridden per test).
Covers transaction category-kind validation, year/month/date-range
filtering and pagination, dashboard summary arithmetic, budget status, cash
flow, and the /transactions/years picker.

e2e/: 6 Playwright specs driving a real browser against a fully separate
throwaway docker-compose stack (run.sh: separate project name, database,
port, and volume, torn down with `down -v` on exit) — regression coverage
for the Recent Transactions month bug, the Card grid-overflow bug, the
frozen-year bug, the Net Worth default range, and the Dashboard->
Transactions deep link, all fixed earlier this session.

Also synced the backend's APP_VERSION constant (stale at 0.15.2, embedded
in exported backups) with the frontend version.
2026-08-16 22:56:53 +03:00
ZProger
98c61d50d8 fix: Net Worth defaults to 5y, Dashboard->Transactions keeps month/year
Net Worth opened on a 30-day window by default, which frequently shows
a dip (short windows don't have enough recorded income relative to
spending) even though the long-term trend is a steady climb —
demotivating for no good reason. Defaults to 5y instead.

Dashboard's "All transactions" link always sent users to the current
month regardless of which month they had selected. It now carries
?year=&month= and TransactionsPage seeds its state from those params
(clamped/validated, falls back to the current month if absent).
2026-08-16 22:31:10 +03:00
ZProger
5b42e3d972 feat: custom year range on Cash Flow and Reports, drop redundant 12m preset
"This year" and "12 months" were two different ways of showing roughly
the same recent window, which read as duplicated. Replaced the "12m"
preset with "custom": pick an arbitrary fromYear-toYear span via a new
YearRangeSelector (two dropdowns built on the same internal component
YearSelector already uses).

Also de-duplicated the RangePreset/computeRange logic that was copied
verbatim between CashFlowPage and ReportsPage into shared
lib/dateRange.ts — both pages had the identical bug/duplication.
2026-08-16 22:23:25 +03:00
ZProger
acff447673 redesign: YearSelector as a compact dropdown next to MonthSelector
The pill-row year switcher from the previous commit added a whole
extra row of buttons above the month pills, which read as cluttered.
Replaced it with a single dropdown button (current year + chevron)
placed to the right of MonthSelector on all three pages, matching how
a lot of finance-app date pickers separate month (frequent) from year
(occasional) navigation.
2026-08-16 22:15:17 +03:00
ZProger
f3fbe4d546 feat: add year switcher to Dashboard, Transactions and Budget
Year was previously hardcoded to the current year on all three pages
(useState with no setter) — only the month could be changed via
MonthSelector, making it impossible to look at or compare older years
even though transaction history goes back further than the current
year.

Adds GET /transactions/years (contiguous range from the earliest
transaction through the current year) and a YearSelector component
matching MonthSelector's style, wired into all three pages alongside
a real year useState.
2026-08-16 22:10:49 +03:00
ZProger
e9eae92975 fix: long unbroken transaction description broke Dashboard layout
Card had no min-width: 0, so as a direct CSS Grid child on the
Dashboard it would grow to fit nowrap (truncate) text instead of
letting the ellipsis kick in — classic grid blowout. Reproduced with
Playwright (scrollWidth 2143px vs 1440px viewport before the fix,
matches after). Fixing it on the shared Card primitive protects every
grid/flex layout that uses it, not just Dashboard.
2026-08-16 22:00:56 +03:00
ZProger
c905cea84e fix: Recent Transactions widget ignored the selected dashboard month
RecentTransactionsCard queried transactions without year/month filters,
so it always showed the latest 6 transactions globally instead of the
ones for the month picked in the MonthSelector. DashboardPage now
passes its year/month state down to the card, which forwards it to
useTransactions.
2026-08-16 21:51:32 +03:00
ZProger
a8c6728860 docs: replace italics with bold in README 2026-08-16 19:22:01 +03:00
ZProger
1c2e67cb86 docs: fill in real clone URL, fix folder name casing 2026-08-16 19:20:34 +03:00
ZProger
bc85310d5e docs: update dashboard screenshot (1.png) to include sidebar 2026-08-16 19:11:34 +03:00
ZProger
f60be1377d docs: add donate link to README 2026-08-16 19:09:50 +03:00
ZProger
64321161b5 docs: add screenshots, drop Roadmap/Community sections
Screenshots section at the top of README (dashboard and net worth
front and center, four supporting views in a grid below). Roadmap and
Community sections removed per request.
2026-08-16 18:54:01 +03:00
ZProger
fe9b079c12 Initial public release
Aurum — a self-hosted personal finance operating system: cash flow,
net worth tracking, budgets, goals, recurring payments, reports,
proactive alerts, and a compound-interest returns calculator, all
bilingual (RU/EN) and mobile-first. Ships as a three-container Docker
Compose stack (Postgres + FastAPI + nginx-served React frontend).

See README.md for setup and CONTRIBUTING.md for development.
2026-08-16 18:39:52 +03:00