Конфигурация и эксплуатационный слой:
- 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.
- Кэш проверки дублей не сбрасывался после импорта: повторный импорт
того же файла в течение 30 секунд (staleTime) сверялся со списком
транзакций, полученным ДО первого импорта, и не находил ни одного
дубля — ровно тот случай, ради которого проверка и делалась.
- Кнопку импорта можно было нажать, пока проверка дублей ещё шла или
шёл фоновый рефетч: дубли проходили мимо фильтра. Теперь кнопка
заблокирована на время запроса, а его ошибка больше не проглатывается
молча, а показывается предупреждением.
- Ручная смена кодировки ломала уже выбранные колонки: заголовки
перечитывались, старый выбор указывал на несуществующее имя,
indexOf давал -1, и все строки молча отбрасывались как «не удалось
распознать дату». Маппинг переугадывается при изменении состава
заголовков.
- Текст «Они не будут импортированы» противоречил включённой галочке
«Всё равно импортировать их».
Пять доработок импорта транзакций из CSV:
1. Проверка дублей при повторном импорте — сверка с уже существующими
транзакциями на счёте за диапазон дат файла (по дате+типу+сумме+
описанию), похожие исключаются из импорта по умолчанию с опцией
всё равно их добавить.
2. Автоопределение кодировки файла (UTF-8 с откатом на Windows-1251)
вместо жёстко зашитого UTF-8 — раньше кириллица из банковских
выгрузок превращалась в кракозябры. Есть ручной выбор на случай
неверного автоопределения.
3. Исправлен баг в parseAmount: суммы с несколькими разделителями
тысяч и без дробной части (например "1,234,567") теряли цифры и
отбрасывались как нераспознанные — теперь распознаются верно.
4. Добавлен явный выбор разделителя дробной части (точка/запятая) на
случай, когда автоопределение не может разрешить неоднозначность.
5. Пример значения из файла показывается прямо под каждым выбором
колонки при сопоставлении — не нужно доходить до превью, чтобы
понять, что в колонке.
Раньше при отсутствии .env docker compose тихо стартовал на дефолтах
из docker-compose.yml (без пароля, дефолтные креды Postgres), никак
не сигнализируя об этом. Добавлен start.sh — обёртка над
docker compose up -d --build, которая при отсутствии .env создаёт его
из .env.example и один раз громко предупреждает об используемых
дефолтах перед стартом контейнеров. README обновлён на использование
этого скрипта.
Дизайн-баннер был легко закрываем и появлялся заново каждую сессию.
Теперь показывается блокирующее модальное окно без кнопки закрытия/
Escape/клика по фону — пройти дальше можно только явным подтверждением
'Я понимаю риски'. Согласие сохраняется в localStorage и не запрашивается повторно.
Раньше предупреждение об отсутствии AURUM_BASIC_AUTH_USER/PASSWORD
было видно только в логах docker-контейнера при старте и терялось,
если пользователь забыл настроить .env. Теперь LoginGate различает
'авторизован' и 'auth вообще не настроен' и во втором случае
показывает заметный баннер прямо в интерфейсе.
На Firefox (Linux) программный .click() по input[type=file] со стилем
display:none может не открывать нативный диалог выбора файла. Теперь
инпут скрыт визуально (opacity + нулевой размер), но остаётся в layout.
Все minor/patch-апдейты одного пакетного менеджера теперь идут одним
PR, а major-обновления — отдельным, чтобы не терялись среди рутинных
и не сыпало десятком PR одновременно.
Создание с дефолтным/кастомным risk_level, правка через PATCH
/assets/{id}, создание/правка/сброс network через PATCH
/crypto/holdings/{id}, partial-update и 404 на неизвестный id.
Ссылается на чек-лист из CONTRIBUTING.md вместо дублирования, плюс
пункт про реальную проверку (не просто "тесты прошли" на словах) и
про отсутствие личных данных/ключей в дифе.
Vitest для чистой логики: lib/format.ts (заодно поймал баг в
trimTrailingZeros на "-0.000") и расчёт разбивки по риску/монете/сети
на вкладке Crypto — вынес их из компонентов в отдельные тестируемые
функции. Тесты подключены в CI. Версия приложения обновлена до 1.1.6.
Свободное текстовое поле на холдинге — какой сети принадлежит конкретная
монета (Ethereum, Tron и т.д.), с редактированием прямо в таблице и
разбивкой % по сетям на отдельной вкладке. Помогает увидеть, что часть
стейблкоинов лежит на рискованных сетях и стоит перевести на безопасные.
Риск (низкий/средний/высокий) задаётся при добавлении монеты и
редактируется прямо в таблице холдингов. Добавлена вкладка "Уровни
риска" в карточке портфеля с разбивкой % по риск-тирам, только для
крипты. Заодно поправлено округление процентов в риск-карточках.
Монеты в таблице активов шли в алфавитном порядке (сортировка по
имени в SQL-запросе) — с импортированными портфелями это выглядело
хаотично. Теперь список отсортирован по cost basis (сколько денег
реально вложено в монету) от большего к меньшему; полностью
проданная позиция уходит в конец списка, а не в начало.
График во вкладке «Криптовалюта» теперь по умолчанию показывает всю
историю, а не 30 дней — короткое окно у портфеля, растущего годами,
часто попадало на локальную просадку и выглядело демотивирующе.
Плитки «Лучший рост» и «Худшее падение» раньше всегда считались за
24 часа. Теперь они следуют за диапазоном, выбранным у графика
(7д/30д/90д/всё время): для 7д/30д используются закэшированные данные
CoinGecko, для «всё время» — метрика за 1 год (дальше бесплатный
тариф CoinGecko историю не отдаёт), а для 90д — отдельный запрос по
каждой монете, который выполняется только когда пользователь реально
выбрал этот период.
Криптовалюту теперь можно разделять на отдельные портфели (например,
долгосрочные инвестиции, накопления на недвижимость, спекулятивные
монеты) вместо одного общего списка — по тому же принципу, что уже
есть у счетов (Account): у портфеля есть имя, цветная метка и
архивация, каждая монета обязательно принадлежит одному портфелю,
один и тот же токен можно держать в нескольких портфелях сразу.
Тип счёта investment не входил в CASH_ACCOUNT_TYPES, из-за чего деньги
на инвестсчёте не попадали ни в блок "Наличные", ни в класс "Инвестиции"
раздела Капитал — просто выпадали из расчёта net worth (issue #14).
Раньше пул-реквесты никак не проверялись автоматически — сломанный PR
можно было смержить, ничего бы не показало проблему. Теперь на каждый PR
и пуш в main гоняются 115 backend-тестов (реальный Postgres в сервис-
контейнере, та же схема, что и в tests/README.md) и сборка фронтенда
(tsc + vite build).
Discussions в репозитории не включены (has_discussions: false), поэтому
../../discussions отдавал 404 в README и CONTRIBUTING. Issues уже
включены и используются для багов — направляем туда же и предложения фич/
неочевидные случаи лицензии.
/api/health now returns a version field, default categories can be deleted
once unused, and split transactions (dividing one purchase across a parent
category's subcategories) were fully undocumented — added a dedicated
section in DOCS.md and a mention in README's feature list. Also fixed the
"update to newer code" command, which pulled a Docker image tag that
doesn't exist for locally-built services instead of the actual git repo.
Версии бэкенда и фронтенда были не синхронизированы и нигде не отображались
в интерфейсе — непонятно было, обновилось ли приложение после пересборки.
Теперь единая версия 1.1.0 приходит с бэкенда через /api/health и мелким
текстом выводится внизу страницы настроек.
Cloning on Windows with core.autocrlf=true converted
frontend/docker-entrypoint.d/20-basic-auth.sh to CRLF. Inside the container
the kernel read the shebang as /bin/sh\r, found no such interpreter, and
nginx crashed with exit 127 in a restart loop, so `docker compose up` never
produced a working web container.
- .gitattributes: LF for everything Linux executes or parses
- frontend/Dockerfile: sed strips CR at build time, covering checkouts made
before .gitattributes existed
FastAPI регистрировал встроенные роуты документации на дефолтных путях
(/docs, /openapi.json), в то время как все остальные роуты подключены с
префиксом /api и только его проксирует nginx. Теперь docs_url/redoc_url/
openapi_url явно указаны под /api, поэтому Swagger UI доступен там же,
где его и ищут (как заявлено в README).
Раньше стандартные категории нельзя было удалить вообще. Теперь можно —
если у категории нет ни одной обычной транзакции, ни строки сплита с
ней. Пользовательские категории удаляются как и раньше, без проверок.
- delete_category() проверяет Transaction.category_id и
TransactionSplit.category_id для стандартных категорий перед
удалением.
- Кнопка удаления в списке категорий больше не блокируется заранее для
стандартных — бэкенд решает, бейдж «стандартная» остался чисто
информационным.
- При отказе (400) показывается понятное сообщение вместо тихого
игнорирования клика.