Конфигурация и эксплуатационный слой:
- nginx: client_max_body_size 64m — восстановление из бэкапа не работало
вовсе, дефолтный лимит в 1 МБ резал дампы (реальный экспорт ~3.4 МБ)
с 413 ещё до backend;
- nginx: заголовки безопасности (nosniff, Referrer-Policy,
Permissions-Policy) и полный Content-Security-Policy. Ради него
inline-скрипт темы вынесен в public/theme-init.js;
- nginx: limit_req на /api/ — перебор пароля Basic Auth шёл со
скоростью сети, ни блокировки, ни задержки не было;
- nginx: server_tokens off;
- порт web привязан к 127.0.0.1, адрес настраивается через
AURUM_BIND_ADDRESS (раньше слушался на всех интерфейсах);
- backend работает от непривилегированного пользователя,
no-new-privileges для backend и web;
- дефолты пароля Postgres сведены к одному значению в обоих сервисах,
в .env.example описана ловушка с инициализацией тома.
Приложение:
- версия убрана из неавторизованного /api/health и отдаётся как
app_version в GET /api/settings;
- /api/docs, /api/redoc и /api/openapi.json отключаются через
AURUM_ENABLE_DOCS;
- текст исключения при неудачном восстановлении уходит в лог, а не
клиенту;
- max_length для notes в схемах транзакций, активов и регулярных
платежей (и в соответствующих полях UI);
- срок «запомнить меня» сокращён с 30 дней до 7;
- pytest обновлён до 9.0.3 (PYSEC-2026-1845), pytest-asyncio до 1.4.0;
- CI: permissions contents:read для GITHUB_TOKEN.
Транзакция на подкатегории показывала только её имя — непонятно было,
что это подкатегория и к какой родительской категории она относится.
Фильтры по категории в Транзакциях и Отчётах тоже перечисляли
подкатегории наравне с родительскими, без иерархии.
- categoryLabels.ts: categoryPath() и buildHierarchicalCategories() —
общая логика, вынесенная из TransactionFormModal.
- Список транзакций и «Последние транзакции» на Dashboard показывают
счёт, затем родителя, затем подкатегорию.
- Фильтр категорий в Транзакциях сгруппирован по типу (как в Отчётах);
оба списка показывают подкатегорию с отступом под родителем.
Раньше сплит позволял смешивать вообще любые категории. Реальная задача —
разложить одну покупку по подкатегориям ОДНОЙ базовой категории (100 € на
продукты: сколько на сладости, сколько на уход). А раз Dashboard и рейтинг
категорий сворачивают подкатегории в родителя, эта разбивка нигде не была
видна.
- _build_splits() требует, чтобы все категории сплита были одной родительской
категорией или её прямыми подкатегориями.
- category_rollup.py отдаёт для каждого родительского тотала список
category-источников, которые в него скинулись (children) — пусто, если
источник один.
- Dashboard и рейтинг категорий: строка с непустым children раскрывается по
клику, показывая вклад каждой подкатегории.
- Форма транзакции: выбор категории при сплите — сначала базовая (верхний
уровень), затем строки сплита выбирают саму базовую или её подкатегорию.
Один чек часто смешивает несколько категорий трат (гипермаркет: часть — продукты,
часть — бытовая химия). Раньше такую транзакцию приходилось огрублять до одной
категории или дробить вручную на несколько записей. Теперь у транзакции есть
второй режим: 2+ доли (TransactionSplit) с суммами по разным категориям, которые
обязаны в точности складываться в сумму транзакции.
- Новая модель TransactionSplit + миграция.
- split_rule_violation() проверяет инварианты на создании и редактировании.
- category_rollup.py — общий helper, которым теперь пользуются Dashboard, отчёт
по категориям и бюджеты, учитывая доли сплитов наравне с обычными транзакциями.
- Фильтр по категории в списке транзакций находит и сплит-транзакции.
- Сплиты попадают в полный бэкап/восстановление БД.
- 13 новых backend-тестов.
- Фронтенд: переключатель «Разбить на несколько категорий» в форме транзакции
с повторяемыми строками и индикатором остатка суммы; строка транзакции в
списке показывает все категории сплита.
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>
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>
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.
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.