mirror of
https://github.com/Zproger/Aurum.git
synced 2026-10-13 11:06:40 +07:00
feat: add a Crypto tab with live CoinGecko prices synced into Net Worth
Track individual coins (identity + quantity) in a dedicated tab, backed by CoinGecko's Demo API (AURUM_COINGECKO_API_KEY). A holding is just another Asset under the hood (asset_class=crypto) with its value written as a regular AssetValuation — the existing Net Worth aggregation needs zero changes to pick it up. Prices never refresh in the background (no scheduler in this stack, same reasoning as recurring transactions): a "Refresh prices" button forces a sync, and GET /crypto/holdings lazily re-syncs at most once every 24h. CoinGecko outages leave existing values untouched (error_key: "unreachable") instead of failing the request. Backup/restore now carries CryptoHolding rows too, so a restore doesn't silently strip a holding's coin identity. Backend: 75/75 tests passing (9 new), including a real identity-map staleness bug the tests caught in the raw-Core-upsert + ORM re-read pattern, fixed via populate_existing=True. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01855PiW5Wd8n4aRW4n9kByY
This commit is contained in:
@@ -7,6 +7,7 @@ const STORAGE_KEY = "aurum:language";
|
||||
const ru = {
|
||||
"nav.dashboard": "Дашборд",
|
||||
"nav.netWorth": "Капитал",
|
||||
"nav.crypto": "Криптовалюта",
|
||||
"nav.roi": "Доходность",
|
||||
"nav.transactions": "Транзакции",
|
||||
"nav.accounts": "Счета",
|
||||
@@ -107,6 +108,25 @@ const ru = {
|
||||
"netWorth.assetsTable.asOf": "на {{date}}",
|
||||
"netWorth.assetsTable.annualRoi": "{{percent}}% годовых",
|
||||
|
||||
"crypto.addButton": "Добавить монету",
|
||||
"crypto.refreshButton": "Обновить цены",
|
||||
"crypto.refreshing": "Обновление…",
|
||||
"crypto.lastSynced": "Обновлено {{time}}",
|
||||
"crypto.neverSynced": "Цены ещё не загружались",
|
||||
"crypto.totalValueLabel": "Всего в криптовалюте",
|
||||
"crypto.empty": "Пока нет ни одной монеты. Добавьте первую, чтобы начать отслеживать.",
|
||||
"crypto.pendingPrice": "цена ещё не получена",
|
||||
"crypto.confirmDelete": "Убрать «{{name}}» из отслеживания?",
|
||||
"crypto.syncError.unreachable": "Не удалось подключиться к CoinGecko — показаны последние известные цены.",
|
||||
"crypto.search.placeholder": "Найти монету (например, Bitcoin или BTC)…",
|
||||
"crypto.search.hint": "Начните вводить название или тикер монеты.",
|
||||
"crypto.search.empty": "Ничего не найдено.",
|
||||
"crypto.search.noApiKey": "Не настроен ключ CoinGecko API — задайте AURUM_COINGECKO_API_KEY в .env (бесплатный ключ на coingecko.com/en/api/pricing).",
|
||||
"crypto.form.quantityLabel": "Количество",
|
||||
"crypto.form.addTitle": "Добавить {{name}}",
|
||||
"crypto.form.editTitle": "Изменить количество: {{name}}",
|
||||
"crypto.form.saveError": "Не удалось сохранить. Проверьте данные и попробуйте снова.",
|
||||
|
||||
"roi.calculator.title": "Калькулятор ROI",
|
||||
"roi.calculator.investmentLabel": "Сумма инвестиции",
|
||||
"roi.calculator.monthlyIncomeLabel": "Доход в месяц",
|
||||
@@ -415,6 +435,7 @@ const ru = {
|
||||
const en: Record<keyof typeof ru, string> = {
|
||||
"nav.dashboard": "Dashboard",
|
||||
"nav.netWorth": "Net Worth",
|
||||
"nav.crypto": "Crypto",
|
||||
"nav.roi": "Returns",
|
||||
"nav.transactions": "Transactions",
|
||||
"nav.accounts": "Accounts",
|
||||
@@ -515,6 +536,25 @@ const en: Record<keyof typeof ru, string> = {
|
||||
"netWorth.assetsTable.asOf": "as of {{date}}",
|
||||
"netWorth.assetsTable.annualRoi": "{{percent}}%/year",
|
||||
|
||||
"crypto.addButton": "Add coin",
|
||||
"crypto.refreshButton": "Refresh prices",
|
||||
"crypto.refreshing": "Refreshing…",
|
||||
"crypto.lastSynced": "Updated {{time}}",
|
||||
"crypto.neverSynced": "Prices haven't loaded yet",
|
||||
"crypto.totalValueLabel": "Total in crypto",
|
||||
"crypto.empty": "No coins yet. Add your first one to start tracking.",
|
||||
"crypto.pendingPrice": "price not fetched yet",
|
||||
"crypto.confirmDelete": "Remove \"{{name}}\" from tracking?",
|
||||
"crypto.syncError.unreachable": "Could not reach CoinGecko — showing the last known prices.",
|
||||
"crypto.search.placeholder": "Find a coin (e.g. Bitcoin or BTC)…",
|
||||
"crypto.search.hint": "Start typing a coin's name or ticker.",
|
||||
"crypto.search.empty": "Nothing found.",
|
||||
"crypto.search.noApiKey": "CoinGecko API key not configured — set AURUM_COINGECKO_API_KEY in .env (free key at coingecko.com/en/api/pricing).",
|
||||
"crypto.form.quantityLabel": "Quantity",
|
||||
"crypto.form.addTitle": "Add {{name}}",
|
||||
"crypto.form.editTitle": "Edit quantity: {{name}}",
|
||||
"crypto.form.saveError": "Failed to save. Check the data and try again.",
|
||||
|
||||
"roi.calculator.title": "ROI Calculator",
|
||||
"roi.calculator.investmentLabel": "Investment amount",
|
||||
"roi.calculator.monthlyIncomeLabel": "Monthly income",
|
||||
|
||||
Reference in New Issue
Block a user